esvdev_logo

25 hábitos de novato en Python - Parte 2

Publicado: 24/04/24

Otros 5 hábitos que reflejan que no conoces bien el lenguaje

Recapitulando

Hora de continuar con más hábitos que es importante que identifiquemos y, en caso de tenerlos, empezar a trabajar para desecharlos. Si viste la primera parte de esta serie, seguro recordarás que el principal motivo de todo esto es mejorar y demostrar a otros y demostrarte a vos mismo/a que estás progresando, que estás dejando de ser ese/a novato/a que escribe código innecesariamente complicado, poco legible o hasta lento.

Como ya he hecho en otros posts, hoy te traigo la traducción de un video que me resultó bastante interesante: “25 hábitos de novato en Python que necesitas dejar”.

Por eso en este post vamos a ver otros 5 hábitos, el porqué es importante dejarlos y, en el caso de algunos, con cuáles hábitos reemplazarlos.

1. Nunca usar comprensiones

No voy a entrar en detalles sobre esta particularidad distintiva del lenguaje, eso te lo dejo a vos para que ejercites el músculo de la investigación (sí, te estoy diciendo que googlees). Pero básicamente la idea de este hábito es reparar sobre el hecho de que todo principiante o que lleva poco tiempo con Python (yo incluido en su momento) difícilmente use comprensión de listas, o, si las usa, usa solamente de listas, siendo que hay otro tipo de comprensiones.

El propósito de las comprensiones en Python es crear código más corto, más claro y aun así contener una poderosa funcionalidad. Acá te va un ejemplo:

cuadrados = []
for numero in range(10):
	cuadrados.append(numero * numero)

Algo que habrás visto y habrás hecho incontable cantidad de veces: crear una lista vacía, y en base a un for loop y la función range ir populando la lista con elementos. En este caso, se añade el resultado de multiplicar la variable de control llamada numero por si misma, o lo que es lo mismo, el cuadrado de un número. Probemos hacer lo mismo, pero con comprensión de listas:

cuadrados = [numero * numero for numero in range(10)]

De 3 líneas de código pasamos a 1 para obtener el mismo resultado y, como plus, de una manera más legible. Lo lindo de todo esto es que podemos usar comprensiones para otras estructuras de datos como lo son diccionarios, sets y hasta funciones generadoras.

comp_dic = {i: i * i for i in range(10)} #comprension de diccionario
comp_set = {i%3 for i in range(10)} #comprension de set
comp_gen = (2*x+5 for x in range(10)} #comprension de funcion generadora

Si es tu primera vez viendo el concepto de comprensiones o recién te enterás que hay otras comprensiones aparte de las de listas, te invito a estudiarlas, practicarlas y aplicarlas en tus proyectos. Pero PARÁ, no todo es color de rosas, porque otro hábito a desechar relacionado con este concepto es el hecho de…

2. Siempre usar comprensiones

Algo que aprendí y quiero transmitirte es que todo en la ingeniería es un tradeoff, es decir, un intercambio. Cuando decidís ir por un camino estás “ganando” en ciertos aspectos pero en detrimento de otros. Los lenguajes de programación no esquivan este principio y, en el caso de las comprensiones de Python, esto es cierto también.

No todo loop que escribas o que encuentres tiene que ser convertido a una comprensión. La verdad es que podrías estar complicando la lectura del código (paradójico, ¿no?) al volverlo más confuso o contra-intuitivo y/o impactando en su rendimiento.

Te comparto un artículo para que veas las desventajas de las comprensiones y que de paso te va a ayudar practicar ese inglés:

3. Verificar tipo de valor usando type() y ==

Quizá estes pensando que para verificar tipos hay que usar la función type(). Razón no te falta, ya que generalmente se suele utilizar esa función junto al operador lógico de comparación == para ver si una variable es de cierto tipo de dato. Ejemplo:

from collections import namedtuple

def chequear_tipo():
	Punto = namedtuple('Punto', ['x','y']
	p = Punto(1, 2)
	
	if type(p) == tuple:
		print("Es una tupla") # debería imprimir esto, ¿no?
	else:
		print("No es una tupla")

En principio parece que todo está bien. La variable p contiene una instancia de la clase Punto, clase que a su vez se inicializa con lo que se conoce como una namedtuple. No te preocupes si no sabes lo que es una “tupla con nombre”, la idea acá es ver lo que hace type().

En teoría, esto debería imprimir “Es una tupla”, ya que p es una tupla. Pero no, no es lo que sucede. ¿Porqué? La respuesta está en el funcionamiento interno de type(). Esta función se utiliza para lo que se conoce como comprobación de tipo estricta (strict type checking). En otras palabras, type() está comprobando si p es estrictamente una tupla pero no lo es, ya que namedtuple es una subclase de tuple, pero no es estrictamente tuple. Curioso, ¿no?

Frente a casos así, lo mejor es usar isinstance(). Esta función también comprueba tipos pero no de manera estricta, sino que tiene en cuenta la herencia, algo importante a tener presente ya que habrá casos donde tengamos que lidiar con tipos que no sean los tipos incorporados de Python (como list , dict, entre otros) sino más bien subclases de estos tipos. Teniendo en cuenta esto, veamos el código anterior pero modificado:

from collections import namedtuple

def chequear_tipo():
	Punto = namedtuple('Punto', ['x','y']
	p = Punto(1, 2)
	
	if isintance(p, tuple):
		print("Es una tupla") # Ahora sí que se va a imprimir esto
	else:
		print("No es una tupla")

Como se puede ver, isinstance() devuelve True ya que esta función no solo comprueba si p es una instancia de la clase tuple sino que también tiene en cuenta las subclases de dicha clase, por lo que la comprobación realizada es más abarcativa y efectiva.

4. Usar == para comprobar None, True y False

Examinemos la siguiente función, que comprueba un argumento x:

def comprobar_igualdad(x):
	if x == None:
		pass
		
	if x == True:
		pass
		
	if x == False:
		pass

Nada del otro mundo. Lo interesante es saber que esto puede escribirse de una forma más pythonica si se utiliza la palabra clave is. En vez de comprobar igualdad, vamos a estar comprobando identidad. De hecho, el operador == es lo que va a realizar detrás de escena (o como se suele decir en inglés, “under the hood”) en cada comprobación para este caso. Entonces mejor ir “directo al grano” y reemplazar el operador de igualdad por el operador de identidad.

def comprobar_igualdad(x):
	if x is None:
		pass
		
	if x is True:
		pass
		
	if x is False:
		pass

Pero hay algo más que descubrí investigando un poquito sobre este tema que seguro te va a ser de utilidad. Veamos un ejemplo para entender otro aspecto muy importante de is.

x = [1, 2, 3]
y = [1, 2, 3]
print(x == y) # True, ya que ambas listas contienen los mismos valores

Con este código, a través del operador == comprobamos que dos listas son iguales ya que contienen el mismo valor. Pero veamos lo que pasa acá:

a = [1, 2, 3]
b = a  
c = [1, 2, 3]

# ¿Qué devolverán estos prints?
print(a is b) 
print(a is c)

Si probaste el código habrás notado que el primer print devuelve True, pero el segundo devuelve False. ¿Cómo es posible si tanto a como b y c tienen los mismos valores? ¿No es que is comprueba si dos cosas son iguales? No, recordá que is compara identidad.

Así es, en el código con las variables a, b y c estamos comprobando identidades. El primer print devuelve True ya que estamos estableciendo que a y b no solo tengan los mismos valores, sino que ambas variables apunten a la misma dirección de memoria. Ahhhh, ¿te agarré ahí no? Claro, lo que sucede es que is, al ver que a y b comparten la misma dirección de memoria nos retorna un True, indicándonos que ambas variables en realidad son el mismo objeto, o en otras palabras, poseen la misma identidad.

El segundo print devuelve False por la misma razón. Tanto a como c podrán tener los mismo valores, pero no comparten la misma posición de memoria y por ende, no tienen la misma identidad.

Bueno, sé que si es la primera vez que te encontrás con este concepto puede marear un poquito, pero te dejo más info para que lo investigues un poco más:

5. Utilizar un “if(bool)” o un “if(len)” para realizar comprobaciones

En este caso particular no hay nada realmente malo en utilizar estas expresiones, pero es bueno notar que Python generalmente ofrece la posibilidad de ser más pragmático en la forma en que escribimos el código. Veamos un poquito qué hace cada expresión y con qué reemplazarlas.

def chequear_bool_o_len(x):
	if bool(x):
		# Acá iria la operacion o tarea a realizar
		
	if len(x) != 0:
		# Acá iria la operacion o tarea a realizar

Nuestra función va a chequear si el argumento x es verdadero o falso mediante evaluarlo con la función bool(). Si es verdadero, entonces se entra en la condición y se hace lo que se tenga que hacer. Pero usemos un poco más la lógica y el pragmatismo del que te hablé recién. La sentencia if solo va a ejecutarse si la condición a evaluar es verdadera. Utilizar bool() no está mal pero demuestra que no conocemos muy bien el lenguaje, ya que podríamos tan solo utilizar ese if de la siguiente manera:

def chequear_bool_o_len(x):
	if x:
		# Acá iria la operacion o tarea a realizar

¿No crees que tiene más sentido y es más directo de esta manera? Si es que x puede contener un valor booleano, no es necesario volver a evaluarlo con bool(). Escribir if x es lo mismo que escribir if True o if x is True, pero mucho más conciso y de una manera más pythonica.

Ahora, hablemos de la otra evaluación, la que se hace con len() != 0. Si x es una estructura de datos, entonces como toda estructura de datos en Python, posee un atributo que indica la longitud de la misma (es decir, la cantidad de elementos que posee) y esto se puede saber a través de len().

El punto es que lo que estamos haciendo acá es comprobar que si x no tiene 0 elementos, o lo que es lo mismo, su longitud es distinta de cero (osea, que tiene cosas adentro), entonces se ejecuten ciertas tareas u operaciones. Pero volvemos a lo mismo que con if bool(x). La manera más concisa y pythonica de comprobar si una estructura tiene elementos, es esta:

def chequear_bool_o_len(x):
	if x:
		# Acá iria la operacion o tarea a realizar

Tranqui no me confundí y copié y pequé el código que vimos anteriormente. La comprobación es la misma. En Python, toda estructura de datos que contenga elementos es considerada truthy, de modo que al hacer if x lo que estamos haciendo es comprobar implícitamente si la longitud de x es distinta de cero. Si x contiene elementos, la condición será True, pero si x está vacía, será False.

Conclusión

Esta fue la segunda parte de los 25 hábitos de novato en Python que es importante que tengamos en cuenta. Si los tenés, te invito a probar los métodos explicados acá para poder sacarte de encima esas costumbres y así puedas generar un código mas pythonico, legible y mantenible. También servirá para mostrar a otros (como pueden ser otros compas dev o quien sabe, un futuro empleador) que tu código va un paso mas allá de lo que se suele enseñar en cursos introductorios del lenguaje.

Ahora, si no los tenés, te invito a compartir este artículo con otros pythonistas para que puedan mejorar su código y contribuirles con conocimiento que puede servirles. No seas tacaño/a y no te quedes los aprendizajes para vos nomás, ayudá a otros 🙂

En la tercera parte vamos a examinar otros 5 hábitos de novato en Python a identificar y corregir, en caso de tenerlos.

Tags:
#python

Escrito por:

Elias Velazquez photo

Elias Velazquez

Python / ETL dev | Data Engineer en progreso | Musico | Nerd de yerbas varias