Hoy publicamos la segunda parte de una traducción del material, que se dedica a las anotaciones de tipo en Python.

→
La primera parte¿Cómo admite Python los tipos de datos?
Python es un lenguaje de tipo dinámico. Esto significa que los tipos de variables utilizadas se verifican solo durante la ejecución del programa. En el ejemplo dado en la parte anterior del artículo, se podía ver que un programador que escribe en Python no necesitaba planificar los tipos de variables y pensar cuánta memoria se necesitaría para almacenar sus datos.
Esto es lo que sucede cuando prepara su código Python para su ejecución: “En Python, el código fuente se convierte, usando CPython, a una forma mucho más simple llamada bytecode. El código de bytes consta de instrucciones que, en esencia, son similares a las instrucciones del procesador. Pero no son ejecutados por el procesador, sino por un sistema de software llamado máquina virtual. (No se trata de máquinas virtuales cuyas capacidades le permiten ejecutar sistemas operativos completos en ellas. En nuestro caso, este es un entorno que es una versión simplificada del entorno disponible para los programas que se ejecutan en el procesador) ".
¿Cómo sabe CPython qué tipos de variables deberían ser cuando prepara un programa para su ejecución? Después de todo, no indicamos estos tipos. CPython no sabe sobre esto. Solo sabe que las variables son objetos. Todo en Python es un
objeto , al menos hasta que resulta que algo tiene un tipo más específico.
Por ejemplo, Python considera como una cadena todo lo que está encerrado entre comillas simples o dobles. Si Python encuentra un número, considera que el valor correspondiente es de tipo numérico. Si intentamos hacer algo con una entidad que no se puede hacer con una entidad de su tipo, Python nos lo hará saber más tarde.
Considere el siguiente mensaje de error que aparece cuando intenta agregar una cadena y un número:
name = 'Vicki' seconds = 4.71; --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-9-71805d305c0b> in <module> 3 4 ----> 5 name + seconds TypeError: must be str, not float
El sistema nos dice que no puede agregar cadenas y números de punto flotante. Además, el hecho de que
name
es una cadena y
seconds
es un número, no interesó al sistema hasta que se intentó agregar
name
y
seconds
.
En otras palabras, se puede describir de la
siguiente manera : “La escritura de pato se usa cuando se realiza la suma. Python no está interesado en qué tipo tiene un objeto en particular. Todo lo que le interesa al sistema es si devuelve una llamada significativa al método de adición. Si no es así, se emite un error ".
¿Qué significaría eso? Esto significa que si escribimos programas en Python, no recibiremos un mensaje de error hasta que el intérprete de CPython participe en la ejecución de la misma línea en la que hay un error.
Este enfoque era inconveniente cuando se aplicaba en equipos que trabajaban en grandes proyectos. El hecho es que en tales proyectos no trabajan con variables separadas, sino con estructuras de datos complejas. En tales proyectos, algunas funciones son llamadas por otras, y esas, a su vez, son llamadas por algunas otras funciones. Los miembros del equipo deberían poder verificar rápidamente el código de sus proyectos. Si no pueden escribir buenas pruebas que detecten errores en los proyectos antes de que se pongan en producción, esto significa que dichos proyectos pueden esperar grandes problemas.
Hablando estrictamente, aquí llegamos a la conversación sobre las anotaciones de tipo en Python.
Podemos decir que, en general, el uso de anotaciones de tipo tiene muchas
fortalezas . Si trabaja con estructuras o funciones de datos complejas que toman muchos valores de entrada, el uso de anotaciones simplifica enormemente el trabajo con estructuras y funciones similares. Especialmente, algún tiempo después de su creación. Si solo tiene una función con un parámetro, como en los ejemplos dados aquí, entonces trabajar con dicha función es, en cualquier caso, muy simple.
¿Qué
sucede si necesitamos trabajar con funciones complejas que toman muchos valores de entrada que son similares a esto de la documentación de
PyTorch :
def train(args, model, device, train_loader, optimizer, epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() optimizer.step() if batch_idx % args.log_interval == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item()))
¿Qué es el
model
? Por supuesto, podemos profundizar en la base del código y descubrir:
model = Net().to(device)
Pero sería bueno si pudiera especificar el tipo de
model
en la firma de la función y salvarse del análisis de código innecesario. Quizás se vería así:
def train(args, model (type Net), device, train_loader, optimizer, epoch):
¿Qué pasa con el
device
? Si revisa el código, puede averiguar lo siguiente:
device = torch.device("cuda" if use_cuda else "cpu")
Ahora nos enfrentamos a la pregunta de qué
torch.device
. Este es un tipo especial de PyTorch. Su descripción se puede encontrar en la
sección correspondiente de la documentación de PyTorch.
Sería bueno si pudiéramos especificar el tipo de
device
en la lista de argumentos de la función. Por lo tanto, ahorraríamos mucho tiempo para aquellos que tendrían que analizar este código.
def train(args, model (type Net), device (type torch.Device), train_loader, optimizer, epoch):
Estas consideraciones pueden continuar por mucho tiempo.
Como resultado, resulta que las anotaciones de tipo son muy útiles para alguien que escribe código. Pero también benefician a quienes leen el código de otras personas. Es mucho más fácil leer el código escrito que el código, para entender cuál tiene que lidiar con lo que es una entidad. Las anotaciones de tipo mejoran la legibilidad del código.
Entonces, ¿qué se ha hecho en Python para llevar el código al mismo nivel de legibilidad que distingue el código escrito en lenguajes estáticamente escritos?
Escribir anotaciones en Python
Ahora estamos listos para hablar en serio sobre las anotaciones de tipo en Python. Al leer los programas escritos en Python 2, se puede ver que los programadores proporcionaron su código con sugerencias que les dicen a los lectores del código qué tipo de variables o valores devuelven las funciones.
Código similar originalmente se veía así:
users = []
Las anotaciones de tipo solían ser simples comentarios. Pero sucedió que Python comenzó a cambiar gradualmente hacia una forma más uniforme de manejar las anotaciones. En particular, estamos hablando de la aparición del documento
PEP 3107 , dedicado a la anotación de funciones.
A continuación, se comenzó a trabajar en
PEP 484 . Este documento, dedicado a las anotaciones de tipo, se desarrolló en estrecha relación con mypy, el proyecto DropBox, que tiene como objetivo verificar los tipos antes de ejecutar scripts. Con mypy, vale la pena recordar que la verificación de tipo no se realiza durante la ejecución del script. Se puede recibir un mensaje de error en tiempo de ejecución si, por ejemplo, intenta hacer algo de un tipo que este tipo no admite. Diga: si intenta cortar un diccionario o llamar al método
.pop()
para una cadena.
Esto es lo que puede aprender de PEP 484 sobre los detalles de la implementación de la anotación: “Aunque estas anotaciones están disponibles en tiempo de ejecución a través del atributo de
annotations
regulares, no se realizan verificaciones de tipo en tiempo de ejecución. En cambio, esta propuesta prevé la existencia de una herramienta de verificación de tipos independiente, con la cual el usuario, si lo desea, puede verificar el código fuente de sus programas. En general, una herramienta de comprobación de tipo similar funciona como un linter muy potente. "Aunque, por supuesto, los usuarios individuales pueden usar una herramienta similar para verificar los tipos en tiempo de ejecución, ya sea para la implementación de la metodología de Diseño por Contrato o para la implementación de la optimización JIT. Pero debe tenerse en cuenta que dichas herramientas aún no han alcanzado la madurez suficiente".
¿Cómo es trabajar con anotaciones de tipo en la práctica?
Por ejemplo, su uso significa la posibilidad de facilitar el trabajo en varios IDE. Por lo tanto,
PyCharm ofrece, según la información de tipo, la finalización del código y la verificación. Características similares están disponibles en VS Code.
Las anotaciones de tipo son útiles por una razón más: protegen al desarrollador de errores estúpidos.
Aquí hay un gran ejemplo de tal protección.
Supongamos que agregamos los nombres de personas al diccionario:
names = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dict, first_name, last_name): dict[first_name] = last_name append_name(names,'Kanye',9)
Si permitimos esto, habrá muchas entradas formadas incorrectamente en el diccionario.
Vamos a arreglar esto:
from typing import Dict names_new: Dict[str, str] = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dic: Dict[str, str] , first_name: str, last_name: str): dic[first_name] = last_name append_name(names_new,'Kanye',9.7) names_new
Ahora verifique este código con mypy y obtenga lo siguiente:
(kanye) mbp-vboykis:types vboykis$ mypy kanye.py kanye.py:9: error: Argument 3 to "append_name" has incompatible type "float"; expected "str"
Se puede ver que mypy no le permite usar el número donde se espera la cadena. Se recomienda a aquellos que quieran usar tales pruebas de manera regular que incluyan mypy donde se realizan pruebas de código en sus sistemas de integración continua.
Escriba sugerencias en varios IDE
Uno de los beneficios más importantes del uso de anotaciones de tipo es que permiten a los programadores de Python usar las mismas funciones de finalización de código en varios IDE que están disponibles para lenguajes estáticamente escritos.
Por ejemplo, suponga que tiene un fragmento de código similar al siguiente. Este es un par de funciones de ejemplos anteriores envueltos en clases.
from typing import Dict class rainfallRate: def __init__(self, hours, inches): self.hours= hours self.inches = inches def calculateRate(self, inches:int, hours:int) -> float: return inches/hours rainfallRate.calculateRate() class addNametoDict: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name self.dict = dict def append_name(dict:Dict[str, str], first_name:str, last_name:str): dict[first_name] = last_name addNametoDict.append_name()
Lo bueno es que nosotros, por iniciativa propia, agregamos descripciones de tipo al código, podemos observar lo que sucede en el programa cuando se llaman los métodos de clase:
Sugerencias de tipo IDEComenzando con las anotaciones de tipo
Puede encontrar buenas recomendaciones en la documentación de mypy con respecto a qué comenzar al comenzar a escribir la base de código:
- Comience con algo pequeño: asegúrese de que algunos archivos que contienen varias anotaciones estén validados con mypy.
- Escribe un script para ejecutar mypy. Esto ayudará a lograr resultados de prueba consistentes.
- Ejecute mypy en las canalizaciones de CI para evitar errores de tipo.
- Anote gradualmente los módulos que se utilizan con mayor frecuencia en el proyecto.
- Agregue anotaciones de tipo al código existente que está modificando; equípalos con el nuevo código que escribes.
- Use MonkeyType o PyAnnotate para anotar automáticamente el código antiguo.
Antes de embarcarse en anotar su propio código, le será útil lidiar con algo.
Primero, necesitará importar el módulo de
mecanografía en el código si usa algo distinto de cadenas, enteros, booleanos y los valores de otros tipos básicos de Python.
En segundo lugar, este módulo permite trabajar con varios tipos complejos. Entre ellos están
Dict
,
Tuple
,
List
y
Set
. Una construcción de la forma
Dict[str, float]
significa que desea trabajar con un diccionario cuyos elementos usan una cadena como clave y un número de coma flotante como valor. También hay tipos llamados
Optional
y
Union
.
En tercer lugar, debe familiarizarse con el formato de las anotaciones de tipo:
import typing def some_function(variable: type) -> return_type: do_something
Si desea saber más acerca de cómo comenzar a aplicar anotaciones de tipo en sus proyectos, me gustaría señalar que se dedican muchos buenos tutoriales a esto.
Aquí está uno de ellos. Lo considero el mejor. Una vez dominado, aprenderá sobre la anotación de código y su verificación.
Los resultados ¿Vale la pena usar anotaciones de tipo en Python?
Ahora preguntémonos si debería usar anotaciones de tipo en Python. En realidad, depende de las características de su proyecto. Esto es lo que dice Guido van Rossum sobre esto en la documentación para mypy: “El propósito de mypy no es convencer a todos de escribir código Python estáticamente escrito. La escritura estática es completamente opcional ahora y en el futuro. El objetivo de Mypy es dar a los programadores de Python más opciones. Es hacer de Python una alternativa más competitiva a otros lenguajes de tipo estático utilizados en grandes proyectos. Es aumentar la productividad de los programadores y mejorar la calidad del software ".
El tiempo requerido para configurar mypy y planificar los tipos necesarios para un determinado programa no se justifica en proyectos pequeños y durante experimentos (por ejemplo, los realizados en Jupyter). ¿Qué proyecto debe considerarse pequeño? Probablemente aquel cuyo volumen, según estimaciones cuidadosas, no exceda de 1000 líneas.
Las anotaciones de tipo tienen sentido en proyectos más grandes. Allí pueden, en particular, ahorrar mucho tiempo. Estamos hablando de proyectos desarrollados por grupos de programadores, sobre paquetes, sobre código, que se utilizan en el desarrollo de sistemas de control de versiones y canalizaciones CI.
Creo que las anotaciones de tipo en los próximos años se volverán mucho más comunes que ahora, sin mencionar el hecho de que bien pueden convertirse en una herramienta cotidiana. Y creo que alguien que comienza a trabajar con ellos antes que los demás no perderá nada.
Estimados lectores! ¿Utiliza anotaciones de tipo en sus proyectos de Python?
