¿Qué son las sugerencias de tipo en Python 3.5?

Una de las características de las que se habla en Python 3.5 se dice que son type hints .

En este artículo se menciona un ejemplo de type hints de type hints y esto también menciona el uso responsable de sugerencias de tipo. ¿Alguien puede explicar más sobre esto y cuándo debería usarse y cuándo no?

Sugeriría leer PEP 483 y PEP 484 y ver esta presentación de Guido en Type Hinting.

En pocas palabras : la sugerencia de tipo es literalmente lo que significan las palabras, insinúa el tipo de objeto (s) que está utilizando .

Debido a la naturaleza dinámica de Python, inferir o verificar el tipo de objeto que se está utilizando es especialmente difícil. Este hecho dificulta que los desarrolladores entiendan qué sucede exactamente en el código que no han escrito y, lo que es más importante, para las herramientas de verificación de tipos que se encuentran en muchos IDEs [PyCharm, PyDev vienen a la mente] que están limitadas debido al hecho de que no tienen ningún indicador de qué tipo son los objetos. Como resultado, recurren a tratar de inferir el tipo con (como se menciona en la presentación) alrededor del 50% de tasa de éxito.


Para tomar dos diapositivas importantes de la presentación de Type Hinting:

¿Por qué tipear sugerencias?

  1. Ayuda a los verificadores de tipos: al indicar qué tipo de objeto quiere que el objeto sea el verificador de tipos puede detectar fácilmente si, por ejemplo, está pasando un objeto con un tipo que no se espera.
  2. Ayuda con la documentación: una tercera persona que vea su código sabrá qué se espera dónde, ergo, cómo usarlo sin obtener los TypeErrors .
  3. Ayuda a los IDE a desarrollar herramientas más precisas y robustas: los entornos de desarrollo serán más adecuados para sugerir métodos apropiados cuando se sepa qué tipo de objeto es. Probablemente hayas experimentado esto con algún IDE en algún momento, golpeando el . y tener métodos / atributos emergentes que no están definidos para un objeto.

¿Por qué usar las damas de tipo estático?

  • Encuentra errores antes : esto es evidente, creo.
  • Cuanto más grande sea tu proyecto, más lo necesitarás : una vez más, tiene sentido. Los lenguajes estáticos ofrecen una robustez y control de los que carecen los lenguajes dynamics. Cuanto más grande y más compleja sea su aplicación, mayor será el control y la previsibilidad (desde un aspecto del comportamiento) que necesita.
  • Los equipos grandes ya están ejecutando análisis estático : supongo que esto verifica los dos primeros puntos.

Como nota final para esta pequeña introducción : esta es una característica opcional y, por lo que entiendo, se introdujo para obtener algunos de los beneficios de la escritura estática.

Por lo general , no necesita preocuparse por eso y definitivamente no necesita usarlo (especialmente en los casos en que usa Python como un lenguaje de scripting auxiliar). Debería ser útil al desarrollar proyectos grandes, ya que ofrece la robustez, el control y las capacidades de depuración adicionales muy necesarias .


Tipo de insinuación con mypy :

Para que esta respuesta sea más completa, creo que una pequeña demostración sería adecuada. mypy , la biblioteca que inspiró las Sugerencias de tipo, ya que se presentan en el PEP. Esto está escrito principalmente para cualquiera que se tope con esta pregunta y se pregunte por dónde empezar.

Antes de hacer eso, permítame reiterar lo siguiente: PEP 484 no hace cumplir nada; es simplemente establecer una dirección para las anotaciones de funciones y proponer directrices sobre cómo se puede / debe realizar la verificación de tipos. Puedes anotar tus funciones e insinuar tantas cosas como quieras; sus scripts aún se ejecutarán independientemente de la presencia de anotaciones porque Python no los usa.

De todos modos, como se señala en el PEP, los tipos de insinuación generalmente deben tomar tres formas:

  • Anotaciones de funciones. ( PEP 3107 )
  • Archivos de código auxiliar para módulos integrados / de usuario.
  • # type: type especial # type: type comentarios de # type: type que complementan las dos primeras formas. (Consulte: ¿Qué son las anotaciones de variables en Python 3.6? Para una actualización de Python 3.6 para # type: type comentarios)

Además, querrá usar sugerencias de tipo junto con el nuevo módulo de typing introducido en Py3.5 . En él, se definen muchos ABCs (adicionales) (clases base abstractas) junto con funciones de ayuda y decoradores para su uso en la verificación estática. La mayoría de los ABCs en collections.abc están incluidos pero en una forma Generic para permitir la suscripción (definiendo un método __getitem__() ).

Para cualquier persona interesada en una explicación más detallada de estos, la mypy documentation está muy bien escrita y tiene muchos ejemplos de código que demuestran / describen la funcionalidad de su verificador; Definitivamente vale la pena leerlo.

Función de anotaciones y comentarios especiales:

Primero, es interesante observar algunos de los comportamientos que podemos obtener al usar comentarios especiales. # type: type especial # type: type comentarios de # type: type se pueden agregar durante las asignaciones de variables para indicar el tipo de un objeto si no se puede inferir directamente. Las asignaciones simples generalmente se deducen fácilmente, pero otras, como las listas (con respecto a su contenido), no pueden.

Nota: Si queremos usar algún derivado de Containers y necesitamos especificar el contenido de ese contenedor, debemos usar los tipos generics del módulo de typing . Estos apoyan la indexación.

 # generic List, supports indexing. from typing import List # In this case, the type is easily inferred as type: int. i = 0 # Even though the type can be inferred as of type list # there is no way to know the contents of this list. # By using type: List[str] we indicate we want to use a list of strings. a = [] # type: List[str] # Appending an int to our list # is statically not correct. a.append(i) # Appending a string is fine. a.append("i") print(a) # [0, 'i'] 

Si agregamos estos comandos a un archivo y los ejecutamos con nuestro intérprete, todo funciona bien e print(a) simplemente imprime el contenido de la lista a . Los # type comentarios de # type han sido descartados, tratados como comentarios simples que no tienen un significado semántico adicional .

Al ejecutar esto con mypy , por otro lado, obtenemos la siguiente respuesta:

 (Python3)jimmi@jim: mypy typeHintsCode.py typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str" 

Indica que una lista de objetos str no puede contener un int , que, hablando estáticamente, es sonido. Esto puede solucionarse respetando el tipo de y solo anexando objetos str o cambiando el tipo de contenido de a para indicar que cualquier valor es aceptable (ejecutado intuitivamente con la List[Any] después de que Any ha sido importado desde la typing ) .

Las anotaciones de funciones se agregan en la forma param_name : type después de cada parámetro en su firma de función y se especifique un tipo de retorno usando la notación de -> type antes de los dos puntos de la función final; todas las anotaciones se almacenan en el atributo __annotations__ para esa función en un útil diccionario. Usando un ejemplo trivial (que no requiere tipos adicionales del módulo de typing ):

 def annotated(x: int, y: str) -> bool: return x < y 

El atributo annotated.__annotations__ ahora tiene los siguientes valores:

 {'y': , 'return': , 'x': } 

Si somos un noobie completo, o estamos familiarizados con los conceptos de Py2.7 y, por lo tanto, desconocemos el TypeError acecho en la comparación de los annotated , podemos realizar otra comprobación estática, detectar el error y evitarnos algunos problemas:

 (Python3)jimmi@jim: mypy typeHintsCode.py typeFunction.py: note: In function "annotated": typeFunction.py:2: error: Unsupported operand types for > ("str" and "int") 

Entre otras cosas, llamar a la función con argumentos inválidos también será atrapado:

 annotated(20, 20) # mypy complains: typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str" 

Estos pueden extenderse básicamente a cualquier caso de uso y los errores detectados se extienden más allá de las llamadas y operaciones básicas. Los tipos que puede verificar son realmente flexibles y simplemente le he dado un pequeño adelanto de su potencial. Una mirada en el módulo de typing , los PEP o los documentos mypy le dará una idea más completa de las capacidades ofrecidas.

Archivos de código auxiliar:

Los archivos de código auxiliar se pueden utilizar en dos casos diferentes que no se excluyen mutuamente:

  • Debe escribir check un módulo para el que no desea modificar directamente las firmas de funciones
  • Desea escribir módulos y realizar una comprobación de tipos, pero además desea separar las anotaciones del contenido.

Los archivos de código auxiliar (con una extensión de .pyi ) son una interfaz anotada del módulo que está creando / desea utilizar. Contienen las firmas de las funciones que desea verificar con el cuerpo de las funciones descartadas. Para tener una idea de esto, dado un conjunto de tres funciones aleatorias en un módulo llamado randfunc.py :

 def message(s): print(s) def alterContents(myIterable): return [i for i in myIterable if i % 2 == 0] def combine(messageFunc, itFunc): messageFunc("Printing the Iterable") a = alterContents(range(1, 20)) return set(a) 

Podemos crear un archivo de código auxiliar randfunc.pyi , en el que podemos colocar algunas restricciones si lo deseamos. El inconveniente es que alguien que ve la fuente sin el código auxiliar no obtendrá esa ayuda de anotación cuando intente comprender qué se supone que se debe pasar a dónde.

De todos modos, la estructura de un archivo de resguardo es bastante simplista: agregue todas las definiciones de funciones con cuerpos vacíos ( pass lleno) y suministre las anotaciones según sus requisitos. Aquí, asummos que solo queremos trabajar con tipos int para nuestros Contenedores.

 # Stub for randfucn.py from typing import Iterable, List, Set, Callable def message(s: str) -> None: pass def alterContents(myIterable: Iterable[int])-> List[int]: pass def combine( messageFunc: Callable[[str], Any], itFunc: Callable[[Iterable[int]], List[int]] )-> Set[int]: pass 

La función de combine da una indicación de por qué podría querer usar anotaciones en un archivo diferente, algunas veces desordenan el código y reducen la legibilidad (gran no-no para Python). Por supuesto, podrías usar alias de tipo, pero eso a veces confunde más de lo que ayuda (así que úsalos sabiamente).


Esto debería familiarizarte con los conceptos básicos de las sugerencias de tipo en Python. A pesar de que el verificador de tipos utilizado ha sido mypy , gradualmente debería comenzar a ver más ventanas emergentes, algunas internamente en IDEs ( PyCharm ) y otras como módulos estándar de Python. Intentaré agregar agregadores adicionales / paquetes relacionados en la siguiente lista cuando los encuentre (o si se sugiere).

Damas que conozco :

  • Mypy : como se describe aquí.
  • PyType : por Google, usa una notación diferente de la que recopilo , probablemente vale la pena echarle un vistazo.

Paquetes / Proyectos Relacionados :

  • typescript: Repo oficial de Python que contiene una variedad de archivos de código auxiliar para la biblioteca estándar.

El proyecto typeshed es en realidad uno de los mejores lugares en los que puede mirar para ver cómo se pueden usar las sugerencias de tipo en un proyecto propio. Tomemos como ejemplo los __init__ __init__ de la clase Counter en el archivo .pyi correspondiente:

 class Counter(Dict[_T, int], Generic[_T]): @overload def __init__(self) -> None: ... @overload def __init__(self, Mapping: Mapping[_T, int]) -> None: ... @overload def __init__(self, iterable: Iterable[_T]) -> None: ... 

Donde _T = TypeVar('_T') se usa para definir clases genéricas . Para la clase Counter , podemos ver que no puede tomar argumentos en su inicializador, obtener una Mapping única de cualquier tipo a un int o tomar un Iterable de cualquier tipo.


Aviso : Una cosa que olvidé mencionar es que el módulo de typing se ha introducido de forma provisional . De PEP 411 :

Un paquete provisional puede tener su API modificada antes de "graduarse" a un estado "estable". Por un lado, este estado proporciona al paquete las ventajas de ser parte formalmente de la distribución de Python. Por otro lado, el equipo de desarrollo central declara explícitamente que no se hacen promesas con respecto a la estabilidad de la API del paquete, que puede cambiar para la próxima versión. Si bien se considera un resultado improbable, dichos paquetes pueden incluso eliminarse de la biblioteca estándar sin un período de desaprobación si las preocupaciones con respecto a su API o mantenimiento resultan bien fundadas.

Así que toma las cosas aquí con una pizca de sal; Tengo dudas de que será eliminado o alterado de manera significativa, pero uno nunca puede saberlo.


** Otro tema en conjunto, pero válido en el ámbito de las sugerencias de tipo: PEP 526 : la syntax de las anotaciones de variables es un esfuerzo por reemplazar los comentarios de # type mediante la introducción de una nueva syntax que permite a los usuarios anotar el tipo de variables en simples declaraciones varname: type .

Consulte ¿Qué son las anotaciones de variables en Python 3.6? , como se mencionó anteriormente, para una pequeña introducción sobre estos.

Agregando a la elaborada respuesta de Jim:

Compruebe el módulo de typing : este módulo admite sugerencias de tipo según lo especificado por PEP 484 .

Por ejemplo, la siguiente función toma y devuelve valores de tipo str y se anota de la siguiente manera:

 def greeting(name: str) -> str: return 'Hello ' + name 

El módulo de typing también soporta:

  1. Tipo aliasing .
  2. Escriba sugerencias para funciones de callback .
  3. Genéricos : las clases básicas abstractas se han ampliado para admitir la suscripción y denotar los tipos esperados para elementos de contenedor.
  4. Tipos generics definidos por el usuario: una clase definida por el usuario se puede definir como una clase genérica.
  5. Cualquier tipo – Cada tipo es un subtipo de Cualquiera.

El nuevo lanzamiento de PyCharm 5 es compatible con sugerencias de tipo. En su publicación del blog (ver sugerencias de Python 3.5 en PyCharm 5 ) ofrecen una gran explicación de qué tipo de sugerencias son y no junto con varios ejemplos e ilustraciones sobre cómo usarlas en su código.

Además, se admite en Python 2.7, como se explica en este comentario :

PyCharm admite el módulo de escritura de PyPI para Python 2.7, Python 3.2-3.4. Para 2.7, tiene que poner sugerencias de tipo en archivos de apéndice * .pyi ya que las anotaciones de funciones se agregaron en Python 3.0 .