Uma introdução às anotações do tipo Python. Continuação


Ilustração de Magdalena Tomczyk


Na primeira parte do artigo, descrevi o básico do uso de anotações de tipo. No entanto, vários pontos importantes não foram considerados. Em primeiro lugar, os genéricos são um mecanismo importante e, em segundo lugar, às vezes pode ser útil descobrir informações sobre os tipos esperados em tempo de execução. Mas eu queria começar com coisas mais simples


Anúncio antecipado


Normalmente, você não pode usar um tipo antes de ser criado. Por exemplo, o código a seguir nem será iniciado:


class LinkedList: data: Any next: LinkedList # NameError: name 'LinkedList' is not defined 

Para corrigir isso, é permitido usar uma string literal. Nesse caso, as anotações serão calculadas diferidas.


 class LinkedList: data: Any next: 'LinkedList' 

Você também pode acessar classes de outros módulos (é claro, se o módulo for importado): some_variable: 'somemodule.SomeClass'


Observação

De um modo geral, qualquer expressão computável pode ser usada como uma anotação. No entanto, é recomendável que eles sejam mantidos o mais simples possível, para que os utilitários de análise estática possam usá-los. Em particular, eles provavelmente não entenderão tipos dinamicamente computáveis. Leia mais sobre restrições aqui: PEP 484 - Dicas de tipo # Dicas de tipo aceitáveis


Por exemplo, o código a seguir funcionará e até anotações estarão disponíveis em tempo de execução, no entanto, mypy lançará um erro nele


 def get_next_type(arg=None): if arg: return LinkedList else: return Any class LinkedList: data: Any next: 'get_next_type()' # error: invalid type comment or annotation 

UPD : No Python 4.0, está planejado incluir o cálculo de anotação de tipo adiado ( PEP 563 ), que eliminará essa técnica com literais de string. com o Python 3.7, você pode ativar um novo comportamento usando a construção de from __future__ import annotations


Funções e objetos chamados


Para situações em que você precisa transferir uma função ou outro objeto (por exemplo, como retorno de chamada), é necessário usar a anotação Callable [[ArgType1, ArgType2, ...], ReturnType]
Por exemplo


 def help() -> None: print("This is help string") def render_hundreds(num: int) -> str: return str(num // 100) def app(helper: Callable[[], None], renderer: Callable[[int], str]): helper() num = 12345 print(renderer(num)) app(help, render_hundreds) app(help, help) # error: Argument 2 to "app" has incompatible type "Callable[[], None]"; expected "Callable[[int], str]" 

É válido especificar apenas o tipo de retorno da função sem especificar seus parâmetros. Nesse caso, as reticências são usadas: Callable[..., ReturnType] . Observe que não há colchetes ao redor das reticências.


No momento, é impossível descrever uma assinatura de função com um número variável de parâmetros de um determinado tipo ou especificar argumentos nomeados.


Tipos genéricos


Às vezes, é necessário salvar informações sobre um tipo, sem corrigi-lo rigidamente. Por exemplo, se você escrever um contêiner que armazena os mesmos dados. Ou uma função que retorna dados do mesmo tipo que um dos argumentos.


Tipos como List ou Callable, que vimos anteriormente, usam o mecanismo genérico. Mas, além dos tipos padrão, você pode criar seus próprios tipos genéricos. Para fazer isso, primeiro, obtenha uma variável TypeVar, que será um atributo do genérico e, em segundo lugar, declare diretamente um tipo genérico:


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" def __init__(self, data: T): self.data = data head_int: LinkedList[int] = LinkedList(1) head_int.next = LinkedList(2) head_int.next = 2 # error: Incompatible types in assignment (expression has type "int", variable has type "LinkedList[int]") head_int.data += 1 head_int.data.replace("0", "1") # error: "int" has no attribute "replace" head_str: LinkedList[str] = LinkedList("1") head_str.data.replace("0", "1") head_str = LinkedList[str](1) # error: Argument 1 to "LinkedList" has incompatible type "int"; expected "str" 

Como você pode ver, para tipos genéricos, a inferência automática do tipo de parâmetro funciona.


Se necessário, um genérico pode ter qualquer número de parâmetros: Generic[T1, T2, T3] .


Além disso, ao definir TypeVar, você pode restringir tipos válidos:


 T2 = TypeVar("T2", int, float) class SomethingNumeric(Generic[T2]): pass x = SomethingNumeric[str]() # error: Value of type variable "T2" of "SomethingNumeric" cannot be "str" 

Elenco


Às vezes, o analisador estático do analisador não pode determinar corretamente o tipo de variável; nesse caso, você pode usar a função de conversão. Sua única tarefa é mostrar ao analisador que a expressão é de um tipo específico. Por exemplo:


 from typing import List, cast def find_first_str(a: List[object]) -> str: index = next(i for i, x in enumerate(a) if isinstance(x, str)) return cast(str, a[index]) 

Também pode ser útil para decoradores:


 MyCallable = TypeVar("MyCallable", bound=Callable) def logged(func: MyCallable) -> MyCallable: @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, args, kwargs) return func(*args, **kwargs) return cast(MyCallable, wrapper) @logged def mysum(a: int, b: int) -> int: return a + b mysum(a=1) # error: Missing positional argument "b" in call to "mysum" 

Trabalhar com anotações de tempo de execução


Embora o intérprete não use anotações por si só, elas estão disponíveis para o seu código enquanto o programa está sendo executado. Para isso, é __annotations__ um atributo de objeto __annotations__ que contém um dicionário com as anotações indicadas. Para funções, são anotações de parâmetro e tipo de retorno, para um objeto, anotações de campo, escopo global, variáveis ​​e suas anotações.


 def render_int(num: int) -> str: return str(num) print(render_int.annotations) # {'num': <class 'int'>, 'return': <class 'str'>} 

get_type_hints também está disponível - ele retorna anotações para o objeto passado para ele, em muitas situações ele corresponde ao conteúdo de __annotations__ , mas existem diferenças: ele também adiciona anotações dos objetos pai (na ordem inversa de __mro__ ) e também permite declarações preliminares dos tipos especificados como strings.


 T = TypeVar("T") class LinkedList(Generic[T]): data: T next: "LinkedList[T]" print(LinkedList.__annotations__) # {'data': ~T, 'next': 'LinkedList[T]'} print(get_type_hints(LinkedList)) # {'data': ~T, 'next': __main__.LinkedList[~T]} 

Para tipos genéricos, informações sobre o tipo em si e seus parâmetros estão disponíveis através dos __args__ e __args__ , mas isso não faz parte do padrão e o comportamento já foi alterado entre as versões 3.6 e 3.7

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


All Articles