Python es sin duda unos de los lenguajes más populares en el mercado, y esto se debe, entre muchas otras cosas, a la sintaxis tan sencilla que posee.
Si bien es cierto la sintaxis de Python es fácil de leer y comprender, el tipado dinámico en muchas ocasiones puede llegar a complicar un poco las cosas.
Que una variable pueda ser definida como un string y 3 líneas de código después almacene un flotante, es algo que para algunos puede causar un poco de conflictos. 😲
>>> variable_a = 'Hola mundo'
>>> print(variable_a)
'Hola mundo'
>>> variable_a = 3.1415
>>> print(variable_a)
variable_a
Si bien es cierto la buenas practicas de programación (Cómo definir una variable por cada una de las operación) pueden mitigar este tipo de problemas; el lenguaje por si solo nos ofrece la posibilidad de utilizar los Anotation Type, o por su traducción al español: Anotaciones de tipos.
Con las anotaciones seremos capaces de indicar, de forma explicita, el tiempo de datos que manejará un objeto. Esto no solo hace que nuestro código sea mucho más fácil de leer, si no que, a la larga, será mucho más fácil de mantener. 🤠
🐍 Las anotaciones fueron introducidas al lenguaje a partir de su versión 3.6.
Las anotaciones podemos utilizarlas en 4 áreas principalmente:
- Variables
- Coleciones
- Funciones
- Clases
Veamos un par de ejemplos.
variables.
Para indicar el tipo de datos de nuestra variable basta con seguir la siguiente estructura:
<variale> : <tipo de dato> = <valor>
>>> a : int = 10
>>> b : float = 3.14
>>> c : bool = False
>>> d : str = 'PyWombat'
>>> a
10
>>> b
3.14
>>> c
False
>>> d
'PyWombat'
Si así lo deseamos podemos crear variables sin inicializar.
>>> message : str
Ojo, al hacer esto hay que tener mucho cuidado, ya que en primera instancia podemos llegar a pensar que la variable comienza con un string vacío, sin embargo esto no es así. Para el interprete la variable un no se encuentra definida.
>>> message : str
>>> message
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-38-1133e3acf0a4> in <module>
----> 1 message
NameError: name 'message' is not defined
Colecciones
Para crear lista podemos usar la palabra reservada list, corchetes e indicamos el tipo de dato a almacenar.
>>> lista : list[int] = []
Podemos inicializar nuestra lista a partir de los valores que deseemos.
>>> calificaciones : list[int] = [10, 10, 8, 9, 9, 8, 10]
>>> calificaciones
[10, 10, 8, 9, 9, 8, 10]
Para las tuplas y diccionarios basta con utilizar la palabra reservada tuple o dict (dependiendo cuál sea el caso), corchetes e indicamos el tipo de dato a almacenar.
Tuplas: Una tupla de números enteros.
>>> puertos : tuple[int] = (3306, 8000, 3000)
>>> puertos
(3306, 8000, 3000)
Diccionarios: Una diccionario llave string valor entero.
>>> usuarios : dict[str:int] = {'usuario1':10, 'usuario2':20}
>>> usuarios
{'usuario1': 10, 'usuario2': 20}
Para las tuplas, en dado caso deseemos ser mucho más específicos en: la cantidad, orden y tipos de datos a almacenar, debemos expresarlo dentro de los corchetes (Esto aplica exactamente igual para las listas, sin embargo una tupla es mucho más propensa a almacenar diferentes tipos de datos).
>>> configuraciones : tuple[int, int, bool, str] = (3306, 8000, False, 'MySQL')
>>> configuraciones
(3306, 8000, False, 'MySQL')
Si deseamos almacenar un mismo tipo de dato, pero desconocemos la cantidad, haremos uso de puntos suspensivos (...).
>>> configuraciones : tuple[int, ...] = (3306, 8000, 1080)
>>> configuraciones
(3306, 8000, 1080)
Hay que tener muy en cuenta que, a pesar de lo expresado en las anotaciones, las cuales facilitan enormemente la legibilidad y mantenimiento del código, estas no son reglas que el intérprete deba seguir; ya que esta en la naturaleza misma del lenguaje ser dinámico, por lo tanto es responsabilidad de los desarrolladores hacer cumplir las anotaciones tal y como fueron definidas. 🥳
Ejemplo de un mal uso de anotaciones.
# Creamos una lista vacía. Se indica que la lista almacenará números enteros
>>> calificaciones : list[int] = []
# Almacenamos diferentes tipos de datos.
>>> calificaciones.append(10)
>>> calificaciones.append(10)
>>> calificaciones.append(False)
>>> calificaciones.append('Demo')
>>> calificaciones
[10, 10, False, 'Demo']
>>> mensaje : str = 'Hola mundo'
>>> mensaje = 10
>>> mensaje
10
Funciones
Al momento de definir una función, es posible indicar los tipos de datos con los cuales trabajará, esto tanto para los parámetros, como para los valores a retornar.
#Para valores por default los caracteres debe encontrarse juntos
>>> def suma(numero1 : int, numero2 :int =10) -> int:
... return numero1 + numero2
>>> suma(10, 20)
30
>>> suma(40)
50
En este ejemplo indico que mi función suma posee 2 parámetros (numero1 y numero2), ambos de tipo entero. He definido que el segundo parámetro posea un valor por default (10). De igual forma, mediante el operador ->
, indico que la función retornará un valor de tipo entero.
Veamos otro ejemplo.
>>> def promedio_calificaciones(calificaciones: list[int]) -> int:
... return sum(calificaciones) // len(calificaciones)
>>> promedio_calificaciones([10, 10, 8, 9, 10])
9
Clases
Para las clases sucede exactamente lo mismo que hemos visto hasta ahora.
class User():
# En caso la función/método no retorne ningún valor None.
def __init__(self, username : str, email : str ) -> None:
self.username = username
self.email = email
def saluda(self) -> str:
return f'Hola, mi username es {self.username}'
Conclusión
Las anotaciones son una gran herramienta que el lenguaje nos provee, no solo facilitan la lectura del código, si no que hace que este sea fácil de testear, y por supuesto mantener. Inclusive, utilizando anotaciones, los desarrolladores que vengan de lenguajes con un tipado estático se les será mucho más ameno integrarse a los equipos de trabajo. 😄