¿Cuáles son las sugerencias de tipo en Python 3.5?

250

Una de las características más comentadas en Python 3.5 son las sugerencias de tipo .

Se menciona un ejemplo de sugerencias de tipo en este artículo y en este, al tiempo que se menciona el uso responsable de sugerencias de tipo. ¿Alguien puede explicar más sobre ellos y cuándo deberían usarse y cuándo no?

Vaulstein
fuente
44
Debe echar un vistazo a PEP 484, que está vinculado desde el registro de cambios oficial .
Stefan
1
@AvinashRaj: Una buena discusión acerca de los lanzamientos que está pasando aquí
Vaulstein
1
Es una pena que este caso de uso de C-API sea completamente ignorado por este PEP 484, particularmente sugerencias de tipo para Cython y Numba.
denfromufa
2
Muy relacionado: ¿Qué son las anotaciones variables en Python 3.6? .
Dimitris Fasarakis Hilliard

Respuestas:

343

Sugeriría leer PEP 483 y PEP 484 y ver esta presentación de Guido sobre Sugerencias de tipo.

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

Debido a la naturaleza dinámica de Python, inferir o verificar el tipo de un objeto que se está utilizando es especialmente difícil. Este hecho dificulta que los desarrolladores comprendan qué está sucediendo exactamente en el código que no han escrito y, lo que es más importante, las herramientas de verificación de tipos que se encuentran en muchos IDE [PyCharm, PyDev vienen a mi mente] que están limitados 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 mencionó en la presentación) alrededor del 50% de tasa de éxito.


Para tomar dos diapositivas importantes de la presentación Sugerencia de tipo:

¿Por qué escribir sugerencias?

  1. Ayuda a los verificadores de tipos : al insinuar qué tipo de objeto desea que sea el objeto, 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, por ejemplo, cómo usarlo sin obtenerlo 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 sepan qué tipo es su objeto. Probablemente haya experimentado esto con algún IDE en algún momento, presionando .y teniendo métodos / atributos emergentes que no están definidos para un objeto.

¿Por qué usar Checkers de tipo estático?

  • Encuentra errores antes : creo que esto es evidente.
  • Cuanto más grande sea su proyecto, más lo necesitará : una vez más, tiene sentido. Los lenguajes estáticos ofrecen una robustez y control de los que carecen los lenguajes dinámicos. Cuanto más grande y compleja sea su aplicación, más control y previsibilidad (desde un aspecto de comportamiento) necesitará.
  • Los grandes equipos ya están ejecutando análisis estáticos : 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 ha introducido 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 script auxiliar). Debería ser útil al desarrollar grandes proyectos, ya que ofrece la robustez, el control y las capacidades de depuración adicionales que tanto se necesitan .


Escriba Sugerencia con mypy :

Para que esta respuesta sea más completa, creo que una pequeña demostración sería adecuada. Usaré mypyla biblioteca que inspiró las Sugerencias de tipo tal como se presentan en el PEP. Esto está escrito principalmente para cualquiera que se encuentre con esta pregunta y se pregunte por dónde comenzar.

Antes de hacerlo, permítanme reiterar lo siguiente: PEP 484 no impone nada; simplemente establece una dirección para las anotaciones de funciones y propone pautas sobre cómo se puede / se debe realizar la verificación de tipo. Puede anotar sus funciones e insinuar tantas cosas como desee; sus scripts se seguirán ejecutando independientemente de la presencia de anotaciones porque Python en sí no las usa.

De todos modos, como se señala en la PEP, los tipos de sugerencias generalmente deben tomar tres formas:

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

Además, querrá usar sugerencias de tipo junto con el nuevo typingmódulo introducido en Py3.5. En él, se definen muchos ABC (clases base abstractas) (adicionales) junto con funciones auxiliares y decoradores para su uso en la comprobación estática. La mayoría ABCsen collections.abcestán incluidas, pero en una Genericforma a fin de permitir la suscripción (mediante la definición de un __getitem__()método).

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

Anotaciones de funciones y comentarios especiales:

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

Nota: Si queremos usar cualquier derivada de Containersy necesitamos especificar los contenidos para ese contenedor, debemos usar los tipos genéricos del typingmódulo. Estos soportan 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 y print(a)solo imprime el contenido de la lista a. Los # typecomentarios 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"

Indicando que una lista de strobjetos no puede contener un int, que, estáticamente hablando, es sólido. Esto se puede solucionar ya sea respetando el tipo de objetos ay solo agregándolos stro cambiando el tipo de contenido de apara indicar que cualquier valor es aceptable (realizado de forma intuitiva con List[Any]después de que Anyse ha importado typing).

Las anotaciones de función se agregan en el formulario param_name : typedespués de cada parámetro en su firma de función y se especifica un tipo de retorno utilizando la -> typenotación antes de la función de finalización de dos puntos; todas las anotaciones se almacenan en el __annotations__atributo para esa función en un práctico formulario de diccionario. Usando un ejemplo trivial (que no requiere tipos adicionales del typingmódulo):

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

El annotated.__annotations__atributo ahora tiene los siguientes valores:

{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

Si somos un novato completo, o estamos familiarizados con los Py2.7conceptos y, en consecuencia, no somos conscientes del TypeErroracecho en la comparación de annotated, podemos realizar otra comprobación estática, detectar el error y ahorrarnos 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 no válidos también quedará 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 he dado un pequeño adelanto de su potencial. Una mirada en el typingmódulo, los PEP o los mypydocumentos le dará una idea más completa de las capacidades ofrecidas.

Archivos Stub:

Los archivos de código auxiliar se pueden usar en dos casos diferentes no mutuamente excluyentes:

  • Debe escribir marque un módulo para el que no desea alterar directamente las firmas de funciones
  • Desea escribir módulos y tener una verificación de tipo, 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 apéndice randfunc.pyi, en el que podemos colocar algunas restricciones si lo deseamos. La desventaja es que alguien que vea la fuente sin el código auxiliar realmente no recibirá esa ayuda de anotación cuando intente entender qué se supone que se debe pasar a dónde.

De todos modos, la estructura de un archivo apéndice es bastante simplista: agregue todas las definiciones de funciones con cuerpos vacíos ( passrellenos) y proporcione las anotaciones basadas en sus requisitos. Aquí, supongamos que solo queremos trabajar con inttipos 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 combinefunción da una indicación de por qué es posible que desee utilizar anotaciones en un archivo diferente, algunas veces abarrotan el código y reducen la legibilidad (gran no-no para Python). Por supuesto, podría usar alias de tipo, pero eso a veces confunde más de lo que ayuda (así que úselos sabiamente).


Esto debería familiarizarlo con los conceptos básicos de Type Hints en Python. A pesar de que el verificador de tipo utilizado ha sido mypygradual, 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 verificadores adicionales / paquetes relacionados en la siguiente lista cuando y si los encuentro (o si se sugiere).

Damas que conozco :

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

Paquetes / proyectos relacionados :

  • typehed: repositorio oficial de Python que alberga una variedad de archivos stub para la biblioteca estándar.

El typeshedproyecto 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__usuarios de la Counterclase en el .pyiarchivo 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 Counterclase podemos ver que no puede tomar argumentos en su inicializador, obtener un solo Mappingde cualquier tipo a uno int o tomar uno Iterablede cualquier tipo.


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

Un paquete provisional puede tener su API modificada antes de "graduarse" en un estado "estable". Por un lado, este estado proporciona al paquete los beneficios de ser parte formal de la distribución de Python. Por otro lado, el equipo central de desarrollo 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 demuestran ser fundadas.

Así que toma las cosas aquí con una pizca de sal; Dudo que se elimine o altere de manera significativa, pero nunca se puede saber.


** Otro tema completo pero válido en el ámbito de las sugerencias de tipo PEP 526: La sintaxis para anotaciones de variables es un esfuerzo para reemplazar los # typecomentarios mediante la introducción de una nueva sintaxis que permite a los usuarios anotar el tipo de variables en varname: typedeclaraciones simples .

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

Dimitris Fasarakis Hilliard
fuente
3
"Debido a la naturaleza altamente dinámica de Python, inferir o verificar el tipo de objeto que se está usando es especialmente difícil". Te refieres a la comprobación estática, ¿verdad?
bsam
53

Agregando a la elaborada respuesta de Jim:

Verifique el typingmódulo : 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 stry se anota de la siguiente manera:

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

El typingmódulo también admite:

  1. Alias ​​de tipo .
  2. Escriba sugerencias para funciones de devolución de llamada .
  3. Genéricos : las clases base abstractas se han ampliado para admitir la suscripción para denotar los tipos esperados de elementos de contenedor.
  4. Tipos genéricos 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.
Ani Menon
fuente
26

El recientemente lanzado PyCharm 5 admite sugerencias de tipo. En su publicación de blog al respecto (consulte Sugerencias de tipo Python 3.5 en PyCharm 5 ) ofrecen una excelente explicación de qué sugerencias de tipo son y qué no, junto con varios ejemplos e ilustraciones sobre cómo usarlas en su código.

Además, es compatible con 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, debe poner sugerencias de tipo en los archivos de apéndice * .pyi, ya que las anotaciones de funciones se agregaron en Python 3.0 .

tsvenson
fuente
0

La sugerencia de tipo es una adición reciente a un lenguaje dinámico donde durante décadas la gente juró convenciones de nombres tan simples como húngaro (etiqueta de objeto con la primera letra b = boolian, c = carácter, d = diccionario, i = entero, l = lista, n = numérico , s = string, t = tuple) no eran necesarios, demasiado engorroso, pero ahora he decidido que, oh, espera ... es demasiado problema usar el lenguaje (type ()) para reconocer objetos, y nuestros IDEs elegantes necesitan ayuda para hacer algo que sea complicado, y que los valores de objeto asignados dinámicamente los vuelven completamente inútiles de todos modos, mientras que una simple convención de nomenclatura podría haber resuelto todo, para cualquier desarrollador, de un simple vistazo.

Noah F. SanTsorvutz
fuente
Para ser sincero, esto suena más como una queja que una respuesta.
Dimitris Fasarakis Hilliard
-1

Las sugerencias de tipo son para mantenimiento y no son interpretadas por Python. En el siguiente código, la línea def add(self, ic:int)no produce un error hasta la siguiente return...línea:

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic
    
c1 = C1()
c1.add(2)

c1.add(c1)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'
 
Leon Chang
fuente