Nuevas anotaciones de tipo en Python 3.8 (Protocolo, Final, TypedDict, Literal)

Python 3.8 salió esta noche y las anotaciones de tipo obtuvieron nuevas características:


  • Protocolos
  • Diccionarios mecanografiados
  • Especificador final
  • Coincidencia de valor fijo

Si aún no está familiarizado con las anotaciones de tipo, le recomiendo que preste atención a mis artículos anteriores ( comienzo , continuación )
Y aunque todos están preocupados por las morsas, quiero hablar brevemente sobre lo último en el módulo de escritura


Protocolos


Python utiliza la escritura de pato y no se requiere que las clases hereden de una determinada interfaz, como en algunos otros idiomas.
Desafortunadamente, antes de la versión 3.8, no podíamos expresar los requisitos necesarios para un objeto usando anotaciones de tipo.
PEP 544 está diseñado para resolver este problema.


Términos como "protocolo iterador" o "protocolo descriptor" ya son familiares y se han utilizado durante mucho tiempo.
Ahora puede describir los protocolos en forma de código y verificar su cumplimiento en la etapa de análisis estático.


Vale la pena señalar que, comenzando con Python 3.6, el módulo de mecanografía ya incluye varios protocolos estándar.
Por ejemplo, SupportsInt (que requiere el método __int__ ), SupportsBytes (requiere __bytes__ ) y algunos otros.


Descripción del protocolo


Un protocolo se describe como una clase regular que hereda de Protocol. Puede tener métodos (incluidos aquellos con implementación) y campos.
Las clases reales que implementan el protocolo pueden heredar de él, pero esto no es necesario.


 from abc import abstractmethod from typing import Protocol, Iterable class SupportsRoar(Protocol): @abstractmethod def roar(self) -> None: raise NotImplementedError class Lion(SupportsRoar): def roar(self) -> None: print("roar") class Tiger: def roar(self) -> None: print("roar") class Cat: def meow(self) -> None: print("meow") def roar_all(bigcats: Iterable[SupportsRoar]) -> None: for t in bigcats: t.roar() roar_all([Lion(), Tiger()]) # ok roar_all([Cat()]) # error: List item 0 has incompatible type "Cat"; expected "SupportsRoar" 

Podemos combinar protocolos usando herencia, creando nuevos.
Sin embargo, en este caso, también debe especificar explícitamente el Protocolo como la clase principal.


 class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr") 

Genéricos, autotipados, invocables


Los protocolos, como las clases regulares, pueden ser genéricos. En lugar de especificar Protocol y Generic[T, S,...] como padres Generic[T, S,...] simplemente puede especificar Protocol[T, S,...]


Otro tipo importante de protocolo es el autoescrito (ver PEP 484 ). Por ejemplo


 C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ... 

Además, se pueden usar protocolos cuando la sintaxis de anotación Callable no es suficiente.
Simplemente describa el protocolo con el método __call__ de la firma deseada


Verificaciones de tiempo de ejecución


Aunque los protocolos están diseñados principalmente para su uso por analizadores estáticos, a veces es necesario verificar si la clase pertenece al protocolo deseado.
Para hacer esto posible, aplique el decorador @runtime_checkable al protocolo y las isinstance / issubclass comenzarán a verificar el cumplimiento del protocolo


Sin embargo, esta función tiene varias restricciones de uso. En particular, los genéricos no son compatibles


Diccionarios mecanografiados


Las clases (en particular, las clases de datos ) o las tuplas con nombre se usan generalmente para representar datos estructurados.
pero a veces, por ejemplo, en el caso de una descripción de estructura json, puede ser útil tener un diccionario con ciertas teclas.
PEP 589 presenta el concepto de TypedDict , que anteriormente estaba disponible en extensiones de mypy


Al igual que las clases de datos o las tuplas escritas, hay dos formas de declarar un diccionario escrito. Por herencia o usando una fábrica:


 class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str}) # same as Book book: Book = {"title": "Fareneheit 481", "author": "Bradbury"} # ok other_book: Book = {"title": "Highway to Hell", "artist": "AC/DC"} # error: Extra key 'artist' for TypedDict "Book" another_book: Book = {"title": "Fareneheit 481"} # error: Key 'author' missing for TypedDict "Book" 

Los diccionarios escritos admiten herencia:


 class BookWithDesc(Book): desc: str 

Por defecto, se requieren todas las claves del diccionario, pero puede deshabilitar esto pasando total=False al crear la clase.
Esto se aplica solo a las claves descritas en la taquilla actual y no afecta a las heredadas


 class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"} # ok 

Usar TypedDict tiene una serie de limitaciones. En particular:


  • las comprobaciones en tiempo de ejecución a través de isinstance no son compatibles
  • las claves deben ser literales o valores finales

Además, tales operaciones inseguras como .clear o del prohibidas con dicho diccionario.
También se puede prohibir trabajar en una clave que no sea literal, ya que en este caso es imposible determinar el tipo de valor esperado


Modificador final


PEP 591 presenta el modificador final (como decorador y anotación) para varios propósitos


  • Designación de una clase de la que es imposible heredar:

 from typing import final @final class Childfree: ... class Baby(Childfree): # error: Cannot inherit from final class "Childfree" ... 

  • La designación del método que tiene prohibido anular:

 from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None: # error: Cannot override final attribute "foo" (previously declared in base class "Base") ... 

  • Designación de una variable (parámetro de función. Campo de clase), que está prohibido reasignar.

 ID: Final[float] = 1 ID = 2 # error: Cannot assign to final name "ID" SOME_STR: Final = "Hello" SOME_STR = "oops" # error: Cannot assign to final name "SOME_STR" letters: Final = ['a', 'b'] letters.append('c') # ok class ImmutablePoint: x: Final[int] y: Final[int] # error: Final name must be initialized with a value def __init__(self) -> None: self.x = 1 # ok ImmutablePoint().x = 2 # error: Cannot assign to final attribute "x" 

En este caso, se permite un código de la forma self.id: Final = 123 , pero solo en el método __init__


Literal


Literal tipo definido en PEP 586 se usa cuando necesita verificar literalmente valores específicos


Por ejemplo, Literal[42] significa que solo se espera 42 como valor.
Es importante que no solo se verifique la igualdad del valor, sino también su tipo (por ejemplo, no será posible usar False si se espera 0).


 def give_me_five(x: Literal[5]) -> None: pass give_me_five(5) # ok give_me_five(5.0) # error: Argument 1 to "give_me_five" has incompatible type "float"; expected "Literal[5]" give_me_five(42) # error: Argument 1 to "give_me_five" has incompatible type "Literal[42]"; expected "Literal[5]" 

En este caso, se pueden transmitir varios valores entre paréntesis, lo que equivale a utilizar Union (los tipos de valores pueden no coincidir).


Las expresiones (por ejemplo, Literal[1+2] ) o valores de tipos mutables no se pueden usar como valores.


Como un ejemplo útil, usar Literal es la función open() , que espera valores de mode específicos.


Manejo de tipos en tiempo de ejecución


Si desea procesar varios tipos de información (como yo ) mientras se ejecuta el programa,
Las funciones get_origin y get_args ahora están disponibles.


Entonces, para un tipo de la forma X[Y, Z,...] tipo X se devolverá como origen, y (Y, Z, ...) como argumentos
Vale la pena señalar que si X es un alias para el tipo incorporado o el tipo del módulo de collections , entonces será reemplazado por el original.


 assert get_origin(Dict[str, int]) is dict assert get_args(Dict[int, str]) == (int, str) assert get_origin(Union[int, str]) is Union assert get_args(Union[int, str]) == (int, str) 

Desafortunadamente, la función para __parameters__ no


Referencias


Source: https://habr.com/ru/post/470774/


All Articles