Python 3.5 introdujo el nuevo módulo de escritura que proporciona soporte de biblioteca estándar para aprovechar las anotaciones de funciones para sugerencias de tipo opcionales. Esto abre la puerta a nuevas e interesantes herramientas para la verificación de tipos estática como mypy y en el futuro, posiblemente, una optimización basada en tipos automatizada. Las sugerencias de tipo se especifican en PEP-483 y PEP-484.
En este tutorial exploro las posibilidades que presentan las sugerencias de tipo y te muestro cómo usar mypy para analizar de forma estática tus programas de Python y mejorar significativamente la calidad de tu código.
Las sugerencias de tipo se crean sobre anotaciones de funciones. Brevemente, las anotaciones de funciones le permiten anotar los argumentos y devolver el valor de una función o método con metadatos arbitrarios. Las sugerencias de tipo son un caso especial de anotaciones de función que anotan específicamente los argumentos de la función y el valor de retorno con información de tipo estándar. Las anotaciones de funciones en general y las sugerencias de tipo en particular son totalmente opcionales. Veamos un ejemplo rápido:
"python def reverse_slice (text: str, start: int, end: int) -> str: devolver el texto [start: end] [:: - 1]
reverse_slice ('abcdef', 3, 5) 'ed "
Los argumentos se anotaron con su tipo y el valor de retorno. Pero es fundamental darse cuenta de que Python ignora esto completamente. Hace que la información de tipo esté disponible a través de anotaciones atributo del objeto de función, pero eso es todo.
python reverse_slice .__ anotaciones 'end': int, 'return': str, 'start': int, 'text': str
Para verificar que Python realmente ignora las sugerencias de tipo, arruinemos totalmente las sugerencias de tipo:
"python def reverse_slice (text: float, start: str, end: bool) -> dict: return text [start: end] [:: - 1]
reverse_slice ('abcdef', 3, 5) 'ed "
Como puede ver, el código se comporta igual, independientemente de las sugerencias de tipo.
DE ACUERDO. Las sugerencias de tipo son opcionales. Python ignora totalmente las sugerencias de tipo. ¿Cuál es el punto de ellos, entonces? Bueno, hay varias buenas razones:
Me sumergiré en el análisis estático con Mypy más tarde. El soporte de IDE ya comenzó con el soporte de PyCharm 5 para sugerencias de tipo. La documentación estándar es excelente para los desarrolladores que pueden entender fácilmente el tipo de argumentos y el valor de devolución con solo mirar la firma de la función y los generadores de documentación automatizados que pueden extraer la información de tipo de las sugerencias..
mecanografía
MóduloEl módulo de escritura contiene tipos diseñados para admitir sugerencias de tipo. ¿Por qué no solo usar los tipos de Python existentes como int, str, list y dict? Definitivamente puedes usar estos tipos, pero debido a la tipificación dinámica de Python, más allá de los tipos básicos, no obtienes mucha información. Por ejemplo, si desea especificar que un argumento puede ser una asignación entre una cadena y un entero, no hay manera de hacerlo con los tipos estándar de Python. Con el módulo de escritura, es tan fácil como:
Mapeo de python [str, int]
Veamos un ejemplo más completo: una función que toma dos argumentos. Uno de ellos es una lista de diccionarios donde cada diccionario contiene claves que son cadenas y valores que son números enteros. El otro argumento es una cadena o un entero. El módulo de escritura permite especificaciones precisas de tales argumentos complicados.
"Python de escribir Importar Lista, Dict, Unión
def foo (a: List [Dict [str, int]], b: Union [str, int]) -> int: "" ”Imprime una lista de diccionarios y devuelve el número de diccionarios" "” si es por ejemplo (b, str): b = int (b) para i en el rango (b): imprimir (a)
x = [dict (a = 1, b = 2), dict (c = 3, d = 4)] foo (x, '3')
['b': 2, 'a': 1, 'd': 4, 'c': 3] ['b': 2, 'a': 1, 'd': 4 , 'c': 3] ['b': 2, 'a': 1, 'd': 4, 'c': 3] "
Veamos algunos de los tipos más interesantes del módulo de mecanografía..
El tipo Llamable le permite especificar la función que puede pasarse como argumentos o devolverse como resultado, ya que Python trata a las funciones como ciudadanos de primera clase. La sintaxis para callables es proporcionar una matriz de tipos de argumentos (nuevamente desde el módulo de escritura) seguido por un valor de retorno. Si eso es confuso, aquí hay un ejemplo:
"python def do_something_fancy (data: Set [float], on_error: Callable [[Exception, int], None]):…
"
La función de devolución de llamada on_error se especifica como una función que toma una excepción y un entero como argumentos y no devuelve nada..
Cualquier tipo significa que un verificador de tipo estático debe permitir cualquier operación, así como la asignación a cualquier otro tipo. Todo tipo es un subtipo de Any..
El tipo de unión que viste anteriormente es útil cuando un argumento puede tener varios tipos, lo cual es muy común en Python. En el siguiente ejemplo, verificar_config () La función acepta un argumento de configuración, que puede ser un objeto de configuración o un nombre de archivo. Si es un nombre de archivo, llama a otra función para analizar el archivo en un objeto Config y devolverlo.
"python def verify_config (config: Union [str, Config]): si es instancia (config, str): config = parse_config_file (config) ...
def parse_config_file (filename: str) -> Config:…
"
El tipo Opcional significa que el argumento puede ser Ninguno también. Opcional [T]
es equivalente a Unión [T, Ninguna]
Hay muchos más tipos que denotan diversas capacidades como Iterable, Iterator, Reversible, SupportsInt, SupportsFloat, Sequence, MutableSequence y IO. Echa un vistazo a la documentación del módulo de mecanografía para la lista completa.
Lo principal es que puede especificar el tipo de argumentos de una manera muy precisa que sea compatible con el sistema de tipo Python con una alta fidelidad y que también permita clases genéricas y abstractas..
A veces desea referirse a una clase en una sugerencia de tipo dentro de uno de sus métodos. Por ejemplo, supongamos que la clase A puede realizar alguna operación de combinación que toma otra instancia de A, se fusiona consigo misma y devuelve el resultado. Aquí hay un intento ingenuo de usar sugerencias de tipo para especificarlo:
"Python clase A: def merge (otro: A) -> A:…
1 clase A: ----> 2 def merge (otros: A = Ninguno) -> A: 3… 4
NameError: el nombre 'A' no está definido "
¿Que pasó? La clase A aún no está definida cuando Python comprueba la sugerencia de tipo para su método merge (), por lo que la clase A no se puede usar en este punto (directamente). La solución es bastante simple, y la he visto utilizada anteriormente por SQLAlchemy. Simplemente especifique la sugerencia de tipo como una cadena. Python entenderá que es una referencia hacia adelante y hará lo correcto:
Python clase A: def merge (otro: 'A' = Ninguno) -> 'A':…
Una desventaja de usar sugerencias de tipo para especificaciones de tipo largo es que puede saturar el código y hacerlo menos legible, incluso si proporciona mucha información de tipo. Puede alias tipos como cualquier otro objeto. Es tan simple como:
"Datos de python = Dict [int, Secuencia [Dict [str, Opcional [List [float]]]]
def foo (data: Data) -> bool:… "
get_type_hints ()
Función de ayudaEl módulo de escritura proporciona la función get_type_hints (), que proporciona información sobre los tipos de argumento y el valor de retorno. Mientras que la anotaciones el atributo devuelve sugerencias de tipo porque son solo anotaciones, todavía recomiendo que use la función get_type_hints () porque resuelve las referencias hacia adelante. Además, si especifica un valor predeterminado de Ninguno en uno de los argumentos, la función get_type_hints () devolverá automáticamente su tipo como Unión [T, Ninguno Tipo] si acaba de especificar T. Veamos la diferencia utilizando el método A.merge () definido anteriormente:
"impresión de pitón (A.merge.anotaciones)
'other': 'A', 'return': 'A' "
los anotaciones atributo simplemente devuelve el valor de anotación como es. En este caso, es solo la cadena 'A' y no el objeto de clase A, a la que 'A' es solo una referencia directa.
"python print (get_type_hints (A.merge))
'regreso':
La función get_type_hints () convirtió el tipo de otro argumento a una unión de A (la clase) y NoneType debido al argumento predeterminado Ninguno. El tipo de retorno también se convirtió a la clase A.
Las sugerencias de tipo son una especialización de las anotaciones de funciones, y también pueden trabajar en paralelo con otras anotaciones de funciones..
Para hacer eso, el módulo de escritura proporciona dos decoradores: @no_type_check y @no_type_check_decorator. los @no_type_check El decorador se puede aplicar a una clase o una función. Agrega el no_type_check atribuir a la función (o cada método de la clase). De esta manera, los verificadores de tipos sabrán ignorar las anotaciones, que no son sugerencias de tipo.
Es un poco engorroso porque si escribe una biblioteca que se usará ampliamente, debe asumir que se usará un verificador de tipos y si desea anotar sus funciones con sugerencias que no sean de tipo, también debe decorarlas con @no_type_check.
Un escenario común cuando se usan anotaciones de funciones regulares es tener un decorador que opere sobre ellas. También desea desactivar la comprobación de tipo en este caso. Una opción es usar el @no_type_check Decorador además de tu decorador, pero que envejece. En cambio, el @no_Type_check_decorator Se puede utilizar para decorar su decorador para que también se comporte como @no_type_check (agrega el no_type_check atributo).
Déjame ilustrar todos estos conceptos. Si intenta obtener get_type_hint () (como lo hará cualquier verificador de tipos) en una función que está anotada con una anotación de cadena normal, get_type_hints () lo interpretará como una referencia hacia adelante:
"Python def f (a: 'alguna anotación'): pasar
imprimir (get_type_hints (f))
SyntaxError: ForwardRef debe ser una expresión - se obtuvo 'alguna anotación "
Para evitarlo, agregue el decorador @no_type_check, y get_type_hints simplemente devuelve un dict vacío, mientras que __ anotaciones__ atributo devuelve las anotaciones:
"python @no_type_check def f (a: 'alguna anotación'): pasar
imprimir (get_type_hints (f))
imprimir (f.anotaciones) 'a': 'alguna anotación' "
Ahora, supongamos que tenemos un decorador que imprime las anotaciones dict. Puedes decorarlo con el @no_Type_check_decorator y luego decora la función y no te preocupes por que algún corrector de tipos llame a get_type_hints () y se confunda. Esta es probablemente una mejor práctica para cada decorador que opera con anotaciones. No olvides el @ functools.wraps, de lo contrario, las anotaciones no se copiarán a la función decorada y todo se derrumbará. Esto se trata en detalle en las anotaciones de funciones de Python 3.
python @no_type_check_decorator def print_annotations (f): @ functools.wraps (f) def decorado (* args, ** kwargs): imprimir (f .__ anotaciones__) devolver f (* args, ** kwargs) devolver decorado
Ahora, puedes decorar la función solo con @print_annotations, y siempre que se llame imprima sus anotaciones..
"python @print_annotations def f (a: 'alguna anotación'): pasar
f (4) 'a': 'alguna anotación' "
Vocación get_type_hints () También es seguro y devuelve un dict vacío..
impresión de python (get_type_hints (f))
Mypy es un comprobador de tipos estático que inspiró las sugerencias de tipos y el módulo de mecanografía. El propio Guido van Rossum es el autor de PEP-483 y coautor de PEP-484.
Mypy se encuentra en un desarrollo muy activo y, a partir de este momento, el paquete en PyPI está desactualizado y no funciona con Python 3.5. Para usar Mypy con Python 3.5, obtenga lo último del repositorio de Mypy en GitHub. Es tan simple como:
bash pip3 instala git + git: //github.com/JukkaL/mypy.git
Una vez que tenga Mypy instalado, simplemente puede ejecutar Mypy en sus programas. El siguiente programa define una función que espera una lista de cadenas. A continuación, invoca la función con una lista de enteros..
"Python de escribir Importar lista
def case_insensitive_dedupe (data: List [str]): "" ”Convierte todos los valores a minúsculas y elimina los duplicados" "” return list (set (x.lower () para x en los datos))
imprimir (case_insensitive_dedupe ([1, 2])) "
Al ejecutar el programa, obviamente falla en el tiempo de ejecución con el siguiente error:
plain python3 dedupe.py Traceback (última llamada más reciente): archivo "dedupe.py", línea 8, en
¿Cuál es el problema con eso? El problema es que no está claro de inmediato, incluso en este caso tan simple, cuál es la causa raíz. ¿Es un problema de tipo de entrada? O tal vez el código en sí está mal y no debería intentar llamar al inferior() Método en el objeto 'int'. Otro problema es que si no tiene una cobertura de prueba del 100% (y, seamos honestos, ninguno de nosotros lo tiene), esos problemas pueden estar al acecho en alguna ruta de código no probada y rara vez utilizada y ser detectada en el peor momento de producción.
La escritura estática, con la ayuda de sugerencias de tipo, le brinda una red de seguridad adicional al asegurarse de que siempre llame a sus funciones (anotadas con sugerencias de tipo) con los tipos correctos. Aquí está la salida de Mypy:
plain (N)> mypy dedupe.py dedupe.py:8: error: el elemento de la lista 0 tiene un tipo incompatible "int" dedupe.py:8: error: el elemento de la lista 1 tiene un tipo incompatible "int" dedupe.py:8: error : El elemento de la lista 2 tiene un tipo "int" incompatible
Esto es sencillo, apunta directamente al problema y no requiere ejecutar muchas pruebas. Otro beneficio de la verificación de tipo estático es que si se compromete con ella, puede omitir la verificación de tipo dinámica, excepto cuando se analiza la entrada externa (lectura de archivos, solicitudes de red entrantes o entrada del usuario). También genera mucha confianza en lo que se refiere a la refactorización..
Las sugerencias de tipo y el módulo de escritura son adiciones totalmente opcionales a la expresividad de Python. Si bien puede que no se adapten a los gustos de todos, para proyectos grandes y equipos grandes pueden ser indispensables. La evidencia es que los equipos grandes ya utilizan la verificación de tipos estática. Ahora que la información de tipo está estandarizada, será más fácil compartir el código, las utilidades y las herramientas que lo utilizan. Los IDE como PyCharm ya lo aprovechan para proporcionar una mejor experiencia de desarrollador.