Python 3 Anotaciones de funciones

Las anotaciones de función son una característica de Python 3 que le permite agregar metadatos arbitrarios a argumentos de función y valor de retorno. Formaban parte de la especificación original de Python 3.0.

En este tutorial, le mostraré cómo aprovechar las anotaciones de funciones de propósito general y combinarlas con los decoradores. También aprenderá acerca de las ventajas y desventajas de las anotaciones de funciones, cuándo es apropiado usarlas y cuándo es mejor usar otros mecanismos como cadenas de documentos y decoradores simples..

Anotaciones de funciones

Las anotaciones de funciones se especifican en PEP-3107. La motivación principal fue proporcionar una forma estándar de asociar metadatos a argumentos de función y valor de retorno. Muchos miembros de la comunidad encontraron casos de uso novedosos, pero utilizaron diferentes métodos, como decoradores personalizados, formatos de cadenas de documentos personalizados y añadiendo atributos personalizados al objeto de función.

Es importante entender que Python no bendice las anotaciones con ninguna semántica. Proporciona simplemente un buen soporte sintáctico para asociar metadatos, así como una forma fácil de acceder a ellos. Además, las anotaciones son totalmente opcionales..

Echemos un vistazo a un ejemplo. Aquí hay una función foo () que toma tres argumentos llamados a, b y c e imprime su suma. Tenga en cuenta que foo () no devuelve nada. El primer argumento una No está anotado. El segundo argumento segundo se anota con la cadena 'anotando b', y el tercer argumento do está anotado con el tipo En t. El valor de retorno se anota con el tipo. flotador. Tenga en cuenta la sintaxis "->" para anotar el valor de retorno.

python def foo (a, b: 'anotando b', c: int) -> float: imprimir (a + b + c)

Las anotaciones no tienen ningún impacto en la ejecución de la función. Llamemos foo () dos veces: una vez con argumentos int y una vez con argumentos de cadena. En ambos casos, foo () hace lo correcto, y las anotaciones son simplemente ignoradas.

"python foo ('Hola', ',', 'Mundo!') Hola, Mundo!

foo (1, 2, 3) 6 "

Argumentos predeterminados

Los argumentos predeterminados se especifican después de la anotación:

"python def foo (x: 'un argumento que por defecto es 5' = 5): imprimir (x)

foo (7) 7

foo () 5 "

Acceso a las anotaciones de funciones

El objeto de función tiene un atributo llamado 'anotaciones'. Es una asignación que asigna cada nombre de argumento a su anotación. La anotación del valor de retorno se asigna a la clave 'retorno', que no puede entrar en conflicto con ningún nombre de argumento porque 'retorno' es una palabra reservada que no puede servir como un nombre de argumento. Tenga en cuenta que es posible pasar un argumento de palabra clave llamado retorno a una función:

"barra de definición de Python (* args, ** kwargs: 'los argumentos de la palabra clave dict'): imprimir (kwargs ['return'])

d = 'return': 4 barra (** d) 4 "

Volvamos a nuestro primer ejemplo y revisemos sus anotaciones:

"python def foo (a, b: 'anotando b', c: int) -> flotar: imprimir (a + b + c)

imprimir (foo.anotaciones) 'c': , 'b': 'anotando b', 'devolver': "

Esto es bastante sencillo. Si realiza anotaciones en una función con una matriz de argumentos y / o una matriz de argumentos de palabras clave, obviamente no podrá anotar argumentos individuales.

"python def foo (* args: 'lista de argumentos sin nombre', ** kwargs: 'dict de argumentos con nombre'): imprimir (args, kwargs)

imprimir (foo.anotaciones) 'args': 'lista de argumentos sin nombre', 'kwargs': 'dict de argumentos con nombre' "

Si lee la sección sobre el acceso a las anotaciones de funciones en PEP-3107, dice que accede a ellas a través del atributo 'func_annotations' del objeto de función. Esto está desactualizado a partir de Python 3.2. No se confunda Es simplemente elanotaciones'atributo.

¿Qué puedes hacer con las anotaciones??

Esta es la gran pregunta. Las anotaciones no tienen un significado estándar o semántico. Hay varias categorías de usos genéricos. Puede usarlos como mejor documentación y mover los argumentos y devolver la documentación de valor fuera de la cadena de documentos. Por ejemplo, esta función:

python def div (a, b): "" "Divide a por b args: a - el dividendo b - el divisor (debe ser diferente de 0) return: el resultado de dividir a por b" "" return a / b

Se puede convertir a:

python def div (a: 'el dividendo', b: 'el divisor (debe ser diferente de 0)') -> 'el resultado de dividir a por b': "" Divide a por b "" "devuelve a / segundo

Si bien se captura la misma información, la versión de anotaciones tiene varios beneficios:

  1. Si cambia el nombre de un argumento, es posible que la versión de la documentación de la documentación no esté actualizada..
  2. Es más fácil ver si un argumento no está documentado.
  3. No es necesario crear un formato especial de documentación de argumentos dentro de la cadena de documentos para ser analizado por las herramientas. los anotaciones atributo proporciona un mecanismo directo, estándar de acceso.

Otro uso del que hablaremos más adelante es la escritura opcional. Python se escribe dinámicamente, lo que significa que puede pasar cualquier objeto como un argumento de una función. Pero a menudo las funciones requerirán que los argumentos sean de un tipo específico. Con las anotaciones puede especificar el tipo justo al lado del argumento de una manera muy natural.

Recuerde que solo especificar el tipo no lo aplicará, y se necesitará trabajo adicional (mucho trabajo). Aun así, incluso solo especificar el tipo puede hacer que la intención sea más legible que especificar el tipo en la cadena de documentos, y puede ayudar a los usuarios a entender cómo llamar a la función.

Otro beneficio de las anotaciones sobre la cadena de documentos es que puede adjuntar diferentes tipos de metadatos como tuplas o dados. Nuevamente, también puede hacer eso con docstring, pero estará basado en texto y requerirá un análisis especial.

Finalmente, puede adjuntar una gran cantidad de metadatos que serán utilizados por herramientas externas especiales o en tiempo de ejecución a través de decoradores. Exploraré esta opción en la siguiente sección..

Anotaciones múltiples

Supongamos que desea anotar un argumento con su tipo y una cadena de ayuda. Esto es muy fácil con anotaciones. Simplemente puede anotar el argumento con un dict que tiene dos teclas: 'tipo' y 'ayuda'.

"python def div (a: dict (type = float, help =" the dividend "), b: dict (type = float, help =" the divisor (debe ser diferente de 0) ")) -> dict (type = float, help = "el resultado de dividir a por b"): "" "Divide a por b" ”” devuelve a / b

imprimir (div.anotaciones) 'a': 'help': 'the dividend', 'type': float, 'b': 'help': 'the divisor (debe ser diferente de 0)', 'type': float , 'return': 'help': 'el resultado de dividir a por b', 'type': float "

Combinando Anotaciones Python y Decoradores

Anotaciones y decoradores van de la mano. Para una buena introducción a los decoradores de Python, echa un vistazo a mis dos tutoriales: Deep Dive Into Python Decorators y Write Your Own Python Decorators.

Primero, las anotaciones se pueden implementar completamente como decoradores. Usted puede simplemente definir una @anotar decorador y haga que tome un nombre de argumento y una expresión de Python como argumentos y luego guárdelos en la función de destino anotaciones atributo. Esto se puede hacer para Python 2 también.

Sin embargo, el verdadero poder de los decoradores es que pueden actuar sobre las anotaciones. Esto requiere coordinación, por supuesto, sobre la semántica de las anotaciones..

Veamos un ejemplo. Supongamos que queremos verificar que los argumentos están dentro de un cierto rango. La anotación será una tupla con el valor mínimo y máximo para cada argumento. Luego necesitamos un decorador que verifique la anotación de cada argumento de palabra clave, verifique que el valor esté dentro del rango y, de lo contrario, generará una excepción. Empecemos con el decorador:

python def check_range (f): def decorado (* args, ** kwargs): para nombre, rango en f .__ anotaciones __. elementos (): min_value, max_value = range si no (min_value <= kwargs[name] <= max_value): msg = 'argument is out of range [ - ]' raise ValueError(msg.format(name, min_value, max_value)) return f(*args, **kwargs) return decorated

Ahora, definamos nuestra función y la decoramos con el @check_range decoradores.

python @check_range def foo (a: (0, 8), b: (5, 9), c: (10, 20)): devuelve a * b - c

Llamemos foo () Con diferentes argumentos y ver qué pasa. Cuando todos los argumentos están dentro de su rango, no hay problema.

python foo (a = 4, b = 6, c = 15) 9

Pero si establecemos c en 100 (fuera del rango (10, 20)), se genera una excepción:

python foo (a = 4, b = 6, c = 100) ValueError: el argumento c está fuera del rango [10 - 20]

¿Cuándo debería usar decoradores en lugar de anotaciones??

Hay varias situaciones en las que los decoradores son mejores que las anotaciones para adjuntar metadatos.

Un caso obvio es si su código necesita ser compatible con Python 2.

Otro caso es si tienes muchos metadatos. Como se vio anteriormente, si bien es posible adjuntar cualquier cantidad de metadatos mediante el uso de dictados como anotaciones, es bastante engorroso y realmente daña la legibilidad.

Finalmente, si se supone que los metadatos deben ser operados por un decorador específico, puede ser mejor asociar los metadatos como argumentos para el decorador mismo..

Anotaciones dinámicas

Las anotaciones son solo un atributo dict de una función.

tipo python (foo .__ anotaciones__) dict

Esto significa que puede modificarlos sobre la marcha mientras se ejecuta el programa. ¿Cuáles son algunos casos de uso? Supongamos que desea averiguar si se utiliza alguna vez el valor predeterminado de un argumento. Cuando se llama a la función con el valor predeterminado, puede incrementar el valor de una anotación. O tal vez desea resumir todos los valores de retorno. El aspecto dinámico se puede hacer dentro de la propia función o por un decorador..

"Python def add (a, b) -> 0: resultado = a + b add.anotaciones['return'] + = resultado return resultado

imprimir (añadir.anotaciones['retorno']) 0

agregar (3, 4) 7 impresiones (agregar.anotaciones['retorno']) 7

agregar (5, 5) 10 impresiones (agregar.anotaciones['volver']) 17 "

Conclusión

Las anotaciones de funciones son versátiles y emocionantes. Tienen el potencial de iniciar una nueva era de herramientas introspectivas que ayudan a los desarrolladores a dominar sistemas cada vez más complejos. También ofrecen al desarrollador más avanzado una forma estándar y legible de asociar los metadatos directamente con argumentos y valor de retorno para crear herramientas personalizadas e interactuar con decoradores. Pero se necesita algo de trabajo para beneficiarse de ellos y utilizar su potencial.

Aprender Python

Aprende Python con nuestra completa guía de tutoriales de Python, ya sea que estés empezando o seas un programador experimentado que busca aprender nuevas habilidades..