O Python 3.8 saiu hoje à noite e as anotações de tipo têm novos recursos:
- Protocolos
- Dicionários digitados
- Especificador final
- Correspondência de valor fixo
Se você ainda não conhece as anotações de tipo, recomendo prestar atenção aos meus artigos anteriores ( início , continuação )
E enquanto todos estão preocupados com as morsas, quero falar brevemente sobre as últimas novidades do módulo de digitação
Protocolos
O Python usa digitação de pato e as classes não precisam herdar de uma certa interface, como em alguns outros idiomas.
Infelizmente, antes da versão 3.8, não era possível expressar os requisitos necessários para um objeto usando anotações de tipo.
O PEP 544 foi projetado para resolver esse problema.
Termos como "protocolo do iterador" ou "protocolo do descritor" já são familiares e são usados há muito tempo.
Agora você pode descrever os protocolos na forma de código e verificar sua conformidade no estágio de análise estática.
Vale ressaltar que, a partir do Python 3.6, o módulo de digitação já inclui vários protocolos padrão.
Por exemplo, SupportsInt
(que requer o método __int__
), SupportsBytes
(requer __bytes__
) e alguns outros.
Descrição do protocolo
Um protocolo é descrito como uma classe regular herdada do protocolo. Pode ter métodos (incluindo aqueles com implementação) e campos.
Classes reais que implementam o protocolo podem herdar dele, mas isso não é necessário.
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()])
Podemos combinar protocolos usando herança, criando novos.
No entanto, nesse caso, você também deve especificar explicitamente Protocolo como a classe pai.
class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr")
Genéricos, de tipo próprio, que podem ser chamados
Protocolos, como classes regulares, podem ser genéricos. Em vez de especificar Protocol
e Generic[T, S,...]
como pai Generic[T, S,...]
você pode simplesmente especificar Protocol[T, S,...]
Outro tipo importante de protocolo é auto-tipado (ver PEP 484 ). Por exemplo
C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ...
Além disso, os protocolos podem ser usados quando a sintaxe da anotação de Callable
não é suficiente.
Apenas descreva o protocolo com o método __call__
da assinatura desejada
Verificações de tempo de execução
Embora os protocolos sejam projetados principalmente para uso de analisadores estáticos, às vezes é necessário verificar se a classe pertence ao protocolo desejado.
Para tornar isso possível, aplique o decorador @runtime_checkable
ao protocolo e as verificações issubclass
/ issubclass
começarão a verificar a conformidade com o protocolo
No entanto, esse recurso possui várias restrições de uso. Em particular, os genéricos não são suportados
Dicionários digitados
Classes (em particular, classes de dados ) ou tuplas nomeadas geralmente são usadas para representar dados estruturados.
mas às vezes, por exemplo, no caso de uma descrição da estrutura json, pode ser útil ter um dicionário com determinadas chaves.
O PEP 589 introduz o conceito de TypedDict
, anteriormente disponível em extensões do mypy
Como classes de dados ou tuplas digitadas, há duas maneiras de declarar um dicionário digitado. Por herança ou usando uma fábrica:
class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str})
Dicionários digitados suportam herança:
class BookWithDesc(Book): desc: str
Por padrão, todas as chaves do dicionário são necessárias, mas você pode desativá-lo passando total=False
ao criar a classe.
Isso se aplica apenas às chaves descritas nas bilheterias atuais e não afeta as informações herdadas.
class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"}
O uso do TypedDict
tem várias limitações. Em particular:
- verificações em tempo de execução através de isinstance não são suportadas
- chaves devem ser literais ou valores finais
Além disso, operações inseguras como .clear
ou del
proibidas nesse dicionário.
O trabalho em uma chave que não é literal também pode ser proibido, pois nesse caso é impossível determinar o tipo de valor esperado
Modificador final
O PEP 591 introduz o modificador final (como decorador e anotação) para diversos fins
- Designação de uma classe da qual é impossível herdar:
from typing import final @final class Childfree: ... class Baby(Childfree):
- A designação do método que é proibido de substituir:
from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None:
- Designação de uma variável (parâmetro de função. Campo de classe), proibida a reatribuição.
ID: Final[float] = 1 ID = 2
Nesse caso, um código no formato self.id: Final = 123
é permitido, mas apenas no método __init__
Literal
Literal
tipo definido no PEP 586 é usado quando você precisa verificar literalmente valores específicos
Por exemplo, Literal[42]
significa que apenas 42 é esperado como um valor.
É importante que não apenas a igualdade do valor seja verificada, mas também seu tipo (por exemplo, não será possível usar False se 0 for esperado).
def give_me_five(x: Literal[5]) -> None: pass give_me_five(5)
Nesse caso, vários valores podem ser transmitidos entre colchetes, o que equivale a usar Union (os tipos de valores podem não coincidir).
Expressões (por exemplo, Literal[1+2]
) ou valores de tipos mutáveis não podem ser usados como valores.
Como um exemplo útil, usar Literal
é a função open()
, que espera valores de mode
específicos.
Manipulação de tipo em tempo de execução
Se você deseja processar vários tipos de informações (como eu ) enquanto o programa está em execução,
As funções get_origin e get_args estão agora disponíveis.
Portanto, para um tipo do formato X[Y, Z,...]
tipo X
será retornado como origem e (Y, Z, ...)
como argumentos
É importante notar que, se X for um alias para o tipo interno ou o tipo do módulo de collections
, ele será substituído pelo 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)
Infelizmente, a função para __parameters__
não foi
Referências