Introdução às anotações de tipo Python

1. Introdução



Ilustração de Magdalena Tomczyk


Segunda parte


Python é uma linguagem com digitação dinâmica e permite manipular livremente variáveis ​​de diferentes tipos. Entretanto, ao escrever código, de uma forma ou de outra, assumimos que tipos de variáveis ​​serão usados ​​(isso pode ser causado por uma restrição do algoritmo ou da lógica de negócios). E para o correto funcionamento do programa, é importante que encontremos erros o mais cedo possível, associados à transferência de dados do tipo errado.


Mantendo a idéia de um pato de digitação dinâmico nas versões modernas do Python (3.6+), ele suporta anotações de tipos de variáveis, campos de classes, argumentos e valores de retorno de funções:



As anotações de tipo são simplesmente lidas pelo interpretador Python e não são mais processadas, mas estão disponíveis para uso em códigos de terceiros e são projetadas principalmente para uso por analisadores estáticos.


Meu nome é Andrey Tikhonov e estou envolvido no desenvolvimento de back-end em Lamoda.


Neste artigo, quero explicar o básico do uso de anotações de tipo e considerar os exemplos típicos implementados ao typing anotações.


Ferramentas de anotação


As anotações de tipo são suportadas por muitos IDEs do Python que destacam código incorreto ou fornecem dicas enquanto você digita.


Por exemplo, é assim que fica no Pycharm:


Erro ao destacar



Dicas:



As anotações de tipo também são processadas pelos linters do console.


Aqui está a saída do pylint:


 $ pylint example.py ************* Module example example.py:7:6: E1101: Instance of 'int' has no 'startswith' member (no-member) 

Mas para o mesmo arquivo que mypy encontrou:


 $ mypy example.py example.py:7: error: "int" has no attribute "startswith" example.py:10: error: Unsupported operand types for // ("str" and "int") 

O comportamento de diferentes analisadores pode variar. Por exemplo, mypy e pycharm tratam de alterar o tipo de uma variável de maneira diferente. Ainda nos exemplos, vou focar na saída do mypy.


Em alguns exemplos, o código pode ser executado sem exceção na inicialização, mas pode conter erros lógicos devido ao uso de variáveis ​​do tipo errado. E em alguns exemplos, pode nem ser executado.


O básico


Diferentemente das versões mais antigas do Python, as anotações de tipo não são escritas em comentários ou docstring, mas diretamente no código. Por um lado, isso quebra a compatibilidade descendente, por outro lado, significa claramente que faz parte do código e pode ser processado de acordo.


No caso mais simples, a anotação contém o tipo diretamente esperado. Casos mais complexos serão discutidos abaixo. Se a classe base for especificada como uma anotação, é aceitável passar instâncias de seus descendentes como valores. No entanto, você pode usar apenas os recursos implementados na classe base.


As anotações para variáveis ​​são escritas após os dois pontos após o identificador. Depois disso, o valor pode ser inicializado. Por exemplo


 price: int = 5 title: str 

Os parâmetros de função são anotados da mesma maneira que as variáveis, e o valor de retorno é indicado após a seta -> e antes dos dois pontos finais. Por exemplo


 def indent_right(s: str, width: int) -> str: return " " * (max(0, width - len(s))) + s 

Para campos de classe, as anotações devem ser especificadas explicitamente ao definir uma classe. No entanto, os analisadores podem produzi-los automaticamente com base no método __init__ , mas nesse caso eles não estarão disponíveis no tempo de execução. Leia mais sobre como trabalhar com anotações em tempo de execução na segunda parte do artigo


 class Book: title: str author: str def __init__(self, title: str, author: str) -> None: self.title = title self.author = author b: Book = Book(title='Fahrenheit 451', author='Bradbury') 

A propósito, ao usar a classe de dados, os tipos de campo devem ser especificados na classe. Mais sobre classe de dados


Tipos incorporados


Embora você possa usar tipos padrão como anotações, muitas coisas úteis estão ocultas no módulo de typing .


Opcional


Se você marcar a variável com o tipo int e tentar atribuí-la como None , haverá um erro:


Incompatible types in assignment (expression has type "None", variable has type "int")


Para esses casos, uma anotação Optional com um tipo específico é fornecida no módulo de digitação. Observe que o tipo de variável opcional é indicado entre colchetes.


 from typing import Optional amount: int amount = None # Incompatible types in assignment (expression has type "None", variable has type "int") price: Optional[int] price = None 

Qualquer


Às vezes, você não deseja limitar os tipos possíveis de uma variável. Por exemplo, se isso realmente não é importante, ou se você planeja fazer o processamento de diferentes tipos por conta própria. Nesse caso, você pode usar a anotação Any . Mypy não jura pelo seguinte código:


 unknown_item: Any = 1 print(unknown_item) print(unknown_item.startswith("hello")) print(unknown_item // 0) 

A questão pode surgir: por que não usar object ? No entanto, nesse caso, supõe-se que, embora qualquer objeto possa ser transferido, ele só pode ser acessado como uma instância do object .


 unknown_object: object print(unknown_object) print(unknown_object.startswith("hello")) # error: "object" has no attribute "startswith" print(unknown_object // 0) # error: Unsupported operand types for // ("object" and "int") 

União


Nos casos em que é necessário permitir o uso de não apenas alguns tipos, mas apenas alguns, você pode usar a anotação de typing.Union com uma lista de tipos entre colchetes.


 def hundreds(x: Union[int, float]) -> int: return (int(x) // 100) % 10 hundreds(100.0) hundreds(100) hundreds("100") # Argument 1 to "hundreds" has incompatible type "str"; expected "Union[int, float]" 

A propósito, a anotação Optional[T] é equivalente a Union[T, None] , embora essa entrada não seja recomendada.


Colecções


O mecanismo de anotação de tipo suporta o mecanismo genérico ( Genéricos , mais na segunda parte do artigo), que permite especificar os tipos de elementos armazenados neles para contêineres.


Listas


Para indicar que uma variável contém uma lista, você pode usar o tipo de lista como uma anotação. No entanto, se você desejar especificar quais elementos a lista contém, essa anotação não será mais adequada. Há typing.List para isso. Semelhante à maneira como especificamos o tipo de uma variável opcional, especificamos o tipo de itens da lista entre colchetes.


 titles: List[str] = ["hello", "world"] titles.append(100500) # Argument 1 to "append" of "list" has incompatible type "int"; expected "str" titles = ["hello", 1] # List item 1 has incompatible type "int"; expected "str" items: List = ["hello", 1] 

Supõe-se que a lista contenha um número indefinido de elementos do mesmo tipo. Mas não há restrições na anotação de um elemento: você pode usar Any , Optional , List e outros. Se o tipo de item não for especificado, será assumido como Any .


Além da lista, anotações semelhantes são para conjuntos: typing.Set e typing.FrozenSet .


Tuplas


As tuplas, diferentemente das listas, são frequentemente usadas para elementos heterogêneos. A sintaxe é semelhante com uma diferença: os colchetes indicam o tipo de cada elemento da tupla individualmente.


Se você planeja usar uma tupla semelhante à lista: armazene um número desconhecido de elementos do mesmo tipo, use as reticências ( ... ).


Anotação Tuple sem especificar os tipos de elemento funciona de maneira semelhante à Tuple[Any, ...]


 price_container: Tuple[int] = (1,) price_container = ("hello") # Incompatible types in assignment (expression has type "str", variable has type "Tuple[int]") price_container = (1, 2) # Incompatible types in assignment (expression has type "Tuple[int, int]", variable has type "Tuple[int]") price_with_title: Tuple[int, str] = (1, "hello") prices: Tuple[int, ...] = (1, 2) prices = (1, ) prices = (1, "str") # Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[int, ...]") something: Tuple = (1, 2, "hello") 

Dicionários


Para dicionários, typing.Dict usado. O tipo de chave e o tipo de valor são anotados separadamente:


 book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"} book_authors["1984"] = 0 # Incompatible types in assignment (expression has type "int", target has type "str") book_authors[1984] = "Orwell" # Invalid index type "int" for "Dict[str, str]"; expected type "str" 

Utilizado de forma semelhante, typing.DefaultDict e typing.OrderedDict


Função Resultado


Você pode usar qualquer anotação para indicar o tipo de resultado da função. Mas existem alguns casos especiais.


Se uma função não retornar nada (por exemplo, como print ), seu resultado será sempre None . Para anotação, também usamos None .


As opções válidas para finalizar essa função seriam: retornar explicitamente None , retornar sem especificar um valor e terminar sem chamar return .


 def nothing(a: int) -> None: if a == 1: return elif a == 2: return None elif a == 3: return "" # No return value expected else: pass 

Se a função nunca retornar (por exemplo, como sys.exit ), você deve usar a anotação NoReturn :


 def forever() -> NoReturn: while True: pass 

Se for uma função de gerador, ou seja, seu corpo contiver uma yield , você poderá usar a Iterable[T] ou Generator[YT, ST, RT] para a função retornada:


 def generate_two() -> Iterable[int]: yield 1 yield "2" # Incompatible types in "yield" (actual type "str", expected type "int") 

Em vez de uma conclusão


Para muitas situações, existem tipos adequados no módulo de digitação, no entanto, não considerarei tudo, pois o comportamento é semelhante aos considerados.
Por exemplo, existe um Iterator como a versão genérica para collections.abc.Iterator , typing.SupportsInt para indicar que o objeto suporta o método __int__ ou Callable para funções e objetos que suportam o método __call__


O padrão também define o formato das anotações na forma de comentários e arquivos stub que contêm informações apenas para analisadores estáticos.


No próximo artigo , gostaria de me debruçar sobre o mecanismo de trabalho de genéricos e processamento de anotações em tempo de execução.

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


All Articles