Cheat Sheet: “Type Hints” e mypy

Banner mypy
Fonte: mypy
Conheça esta Cheat Sheet traduzida do mypy com tudo o que você precisa para nunca mais errar ao usar Type Hints e Function Annotations no Python.

Há alguns posts estamos falando sobre Type Hints, Functions Annotations e sobre as bibliotecas mypy e typing. Caso queira entender um pouco mais sobre esses conceitos e principalmente ver as oportunidades e os benefícios de se usar estas abordagens, leia os posts Python tipado? Introdução `às “Type Hints” e Python tipado? “Type Hints” e “Function Annotations”.

Código em Python do MyPy.
Código em Python do MyPy.

Esperamos, com esse post, não deixar nenhuma dúvida na hora de usar as Type Hints e as Function Annotations.

Cheat Sheet de Type Hints

Muitos termos em Inglês para uma comunidade que tem como objetivo principal trazer conteúdo em Português, né? Às vezes é inevitável nesse nosso mundo da tecnologia, mas vamos tentar explicar cada um desses termos.

Nos posts listados acima podemos encontrar maiores detalhes sobre as Type Hints, mas elas são basicamente, “dicas de tipos” (em tradução livre) e servem para nos dizer quais os tipos de objeto esperados e retornados por variáveis, funções e métodos. As Function Annotations são uma nova sintaxe que nos permitem colocar essas Type Hints no código por meio de “anotações de funções” (também em tradução livre).

O termo Cheat Sheet eu acredito que venha do mundo dos jogos, mas falando de programação, ele pode ser traduzido como uma folha de referência, mas é uma espécie de guia rápido que resume os principais conceitos e sintaxes de uma determinada linguagem de programação ou software. Costuma ser um recurso muito útil para programadores que desejam ter rapidamente à mão informações importantes sobre a linguagem em questão, sem precisar consultar manuais ou documentações mais detalhadas.

E aqui vamos compartilhar com vocês a Cheat Sheet oficial do mypy, que está disponível no repositório oficial deles no GitHub sob a licença MIT, que nos permite traduzir o seu conteúdo sem restrições. Vamos ver como esta Cheat Sheet vai nos ajudar com as anotações de funções e tipos no Python.

Obs: Todo o texto abaixo foi traduzido do conteúdo original, exceto as notas de tradução explicitadas.

Variáveis

Tecnicamente, muitas das anotações de tipos exibidas abaixo são redundantes, dado que o mypy normalmente consegue inferir o tipo de dados da variável a partir do seu valor. Veja a página Type inference and type annotations (Inglês) para mais detalhes.

# É assim que se declara o tipo de uma variável
idade: int = 1

# Você não precisa inicializar a variável para anotar
idade: int # Ok (sem valor assignado em execução)

# Esta forma é útil para condicionais
menor: bool
if idade < 18:
    menor = True
else:
    menor = False

Tipos nativos do Python

# Para a maioria dos tipos, só usamos o nome do tipo
x: int = 1
x: float = 1.0
x: bool = True
x: str = "Teste"
x: bytes = b"Teste"

# Para coleções/estruturas de dados no Python 3.9+, 
# o tipo dos items da coleção vai entre colchetes
x: list[int] = [1, 2, 3]
x: set[int] = {4, 5 ,6}

# Para dicionários, precisamos dos tipos de ambos 
# chaves e valores
x: dict[str, float] = {"campo": 2.0} # Python 3.9+

# Para tuplas de tamanho fixo, especificamos o tipo 
# de cada elemento
x: tuple[int, str, float] = (1, "dois", 3.0) # Python 3.9+

# Para tuplas de tamanho variável, usamos um 
# tipo e reticências
x: tuple[int, ...] = (1, 2, 3) # Python 3.9+
from typing import List, Set, Dict, Tuple

# Para Python 3.8 e anteriores, o nome do tipo da
# coleção é com letra maiúscula e precisa ser importado
# do módulo 'typing'

x: List[int] = [1, 2, 3]
x: Set[int] = {4, 5, 6}
x: Dict[str, float] = {"campo": 2.0}
x: Tuple[int, str, float] = (1, "dois", 3.0)
x: Tuple[int, ...] = (1, 2, 3)
from typing import Union

# Para Python 3.10+, usamos o operador | quando
# o objeto pode ser de um ou mais tipos
# Exemplo abaixo: int OU string
x: list[int | str] = [3, 5, "André", "Lopes"] # Python 3.10+

# Para versões anteriores, usar Union
x: list[Union[int, str]] = [3, 5, "Texto", "divertido"]
from typing import Optional

# Usamos Optional[x] para um valor que pode ser None
# Optional[x] é o mesmo que [X | None] ou Union[X, None]
x: Optional[str] = "texto" if uma_condicao() else None

# Mypy entende que um valor não pode ser None em um if
if x is not None:
    print(x.upper())

# Se um valor não pode nunca ser None, devemos usar assert
assert x is not None
print(x.upper())

Funções

from typing import Callable

# É assim que anotamos a definição de uma função
def vira_string(num: int) -> str:
    return str(num)

# E aqui especificamos múltiplos argumentos
def soma(num1: int, num2: int) -> int:
    return num1 + num2

# Se uma função não retorna um valor, usamos None como tipo 
# de retorno. O valor padrão do argumento vai depois da anotação
def animado(texto: str, animacao: int = 10) -> None:
    print(text + "!" * animacao)

# É assim que anotamos o valor de uma "callable" (função)
x: Callable[[int, float], float] = funcao
from typing import Iterator

# Uma função geradora que faz o "yield" de ints é apenas
# uma função que gera um iterável de ints, anotamos assim
def g(n: int) -> Iterator[int]:
    i = 0
    while i < n:
        yield i
        i += 1
from typing import Union, Optional

# Nós podemos, claro, dividir as anotações 
# da função em múltiplas linhas
def envia_email(
    endereco: Union[str, list[str]],
    remetente: str,
    cc: Optional[list[str]],
    bcc: Optional[list[str]],
    assunto: str = "",
    corpo: Optional[list[str]] = None
    ) -> bool:

...
# Aqui cada arg posicional e cada keyword arg é str
def chamada(self, *args: str, **kwargs: str) -> str:
    reveal_type(args) # Tipo relevado é "tuple[str, ...]"
    reveal_type(kwargs) # Tipo revelado é "dict[str, str]"
    request = faz_request(*args, **kwargs)
    return self.query_na_api(request)

Classes

class MinhaClasse:
    # Nós podemos opcionalmente declarar variáveis
    # de instância no corpo da classe
    attr: int

    # Esta é uma variável de instância com valor padrão
    porcentagem: int = 100

    # O método "__init__" não retorna nada, então recebe
    # o tipo None 
    def __init__(self) -> None:
        ...

    # Para métodos de instâncias, omitir o tipo para "self"
    def meu_metodo(self, num: int, texto: str) -> str:
        return num * texto
# Classes definidas pelo usuário são tipos válidos
# para as anotações
x: MinhaClasse = MinhaClasse()

# Podemos também declarar o tipo dos atributos 
# do método "__init__"
classe Caixa:
    def __init__(self) -> None:
        self.itens: list[str] = []

# Podemos usar a anotação ClassVar para declarar
# variáveis de classe
class Carro:
    assentos: ClassVar[int] = 4
    passageiros: ClassVar[list[str]]
# Se quisermos atributos dinâmicos na classe,
# precisamos sobrescrever "__setattr__" ou "__getattr__"
# "__getattr__" permite acesso dinâmico aos nomes
# "__setattr__" permite atribuição dinâmica aos nomes
class A:
    # Isso irá permitir atribuir a qualquer A.x, se x for do
    # mesmo tipo que "valor" (usar "valor: Any" para 
    # permitir tipos arbitrários
    def __setattr__(self, name: str, value: int) -> None: ...

    # Isso irá permitir acesso a qualquer A.x, se x for
    # compatível com o tipo do retorno
    def __getattr__(self, name: str) -> int: ...

a.foo = 42 # Funciona
a.bar = "Texto" # Falha na verificação de tipo

Quando você está confuso ou as coisas estão complicadas

# Para descobrir qual tipo o mypy inferiu para uma 
# expressão em qualquer lugar do seu código, basta
#usar a função "reveal_type()". O mypy irá imprimir
# uma mensagem de erro falando o tipo; remova a
# função antes de continuar executando o código.
reveal_type(1) # Tipo revelado é "builtins.int"
from typing import Optional

# Se formos inicializar uma variável com um container
# vazio ou "None", talvez precisemos ajudar um pouco 
# o mypy fazendo uma anotação de tipo explícita
x: list[str] = []
x: Optional[str] = None
from typing import Any

# Use a anotação Any quando não souber o tipo de
# alguma coisa ou quando for dinâmico demais para
# poder definir um tipo específico
x: Any = funcao_misteriosa()

# Mypy vai deixar fazer qualquer coisa com x!
x.qualquer() * x["voce"] + x("quer") - any(x) and all(x) is super
# Sem erros no código acima
# Use um comentário de "type: ignore" para suprimir
# erros em uma determinada linha quando o código
# confundir o mypy ou gerar um bug inesperado.
# É uma boa prática adicionar um comentário explicando
# o problema com o código nesta linha
x = funcao_confusa() # type: ignore  # a função retorna... 
from mypy import cast

# "cast()" é uma função auxiliar que nos permite
# sobrescrever o tipo inferido em uma expressão.
# A função serve apenas para o mypy, não tem 
# efeito em tempo de execução de código
a = [4]
b = cast(list[int], a) # Passa sem erros
c = cast(list[str], a) # Passa sem erros, apesar de ser mentira

reveal_type(c) # Tipo revelado é "builtins.list[builtins.str]"

print(c) # Ainda imprime [4], o objeto não é alterado
from mypy import TYPE_CHECKING

# Use "TYPE_CHECKING" caso queira ter código
# que o mypy pode ver mas que não será executado
# em tempo de execução (ou ter código que o mypy
# não pode ver)
if TYPE_CHECKING:
    import json
else:
    import orjson as json

Em alguns casos as anotações de tipos podem causar erros em tempo de execução, veja a página Annotation issues at runtime (Inglês) para lidar com esse tipo de problema.

“Duck Types” Padrão

O que são “Duck Types”?

“Duck Types” em Python é uma maneira de definir a funcionalidade de um objeto pela sua interface e funcionalidades, em vez de pela sua classe específica. Isso significa que, ao invés de verificar se um objeto é uma instância de uma determinada classe, verificamos se o objeto tem os atributos e métodos que precisa para realizar a tarefa desejada. O termo vem do Inglês por conta da expressão do Teste do Pato: “se anda como um pato, nada como um pato e faz quack como um pato, então é um pato”. Em outras palavras, se um objeto tem a aparência e comportamento de um pato, então é tratado como um pato, independente da sua classe específica no código Python. (Nota do tradutor)

Em códigos Python típicos, muitas funções que podem receber uma lista ou dicionário como argumento só precisam que os seus argumentos sejam de alguma forma “tipo uma lista” ou “tipo um dicionário”. O significado específico de algo que é “tipo uma lista” ou “tipo um dicionário” ( ou “tipo qualquer outra coisa”) é chamado de “Duck Type”, e muitos “duck types” que são comuns no “Python idiomático” são padronizados.

O que é “Python idiomático”?

O termo é uma tradução do termo em Inglês “idiomatic Python”, e se refere à maneira de escrever código em Python de acordo com as convenções e melhores práticas recomendados pela comunidade de desenvolvedores. Isso ajuda a tornar o código mais legível e fácil de entender, além de ser mais eficiente e fácil de manter. (Nota do tradutor)
from typing import Mapping, MutableMapping, Sequence, Iterable

# Use "Iterable" para iteráveis genéricos (qualquer coisa que
# seja utilizável em um loop for). Use "Sequence" onde uma 
# sequência é requerida (que suporte "len()" e "__getitem__"

def f(numeros: Iterable[int]) -> list[str]:
    return [str(x) for x in numeros]

f(range(1, 3))


# "Mapping" descreve um objeto "tipo um dicionário"
# (com "__getitem__") que não é mutável.
# "MutableMappig" (com "__setitem__") descreve um
# que seja mutável.
def f(meu_mapa: Mapping[int, str]) -> list[int]:
    meu_mapa[5] = "talvez" # mypy vai reclamar desta linha
    return list(meu_mapa.keys())

f({3: "sim", 4: "não"})

def f(meu_mapa: MutableMapping[int, str]) -> set[str]:
    meu_mapa[5] = "talve" # mypy não reclama aqui
    return set(meu_mapa.values())

f({3: "sim", 4: "não"})

Nós podemos até criar nossos próprios “duck types”, para saber mais leia: Protocols and structural subtyping (Inglês).

Corrotinas e asyncio

O que são corrotinas e asyncio?

Corrotinas são funções que podem pausar a sua execução e retomar mais tarde em um ponto específico, o que é útil em muitas situações, como quando o código precisa aguardar por eventos ou interações com o usuário. As corrotinas são implementadas no Python usando o módulo asyncio e podem tornar o código mais eficiente e mais fácil de ler e manter. (Nota do tradutor)

Veja a página Typing async/await (Inglês) para todos os detalhes sobre tipagem de corrotinas e código assíncrono.

import asyncio

# Uma corrotina é tipada como uma função normal
async def contagem(tag: str, cont: int) -> str:
    while cont > 0:
        print(f"T-menos {cont} ({tag})")
        await asyncio.sleep(0.1)
        cont -= 1
    return "FOGO!"

Miscelâneos

import sys
from typing import IO

# Use "IO[]" para funções que devam aceitar ou
# retornar qualquer objeto que venha de uma 
# chamada "open()" (IO[] não distingue entre
# os modos de leitura, escrita, ou outros)
def get_sys_IO(mode: str = "w") -> IO[str]:
    if mode == "w":
        return sys.stdout
    elif mode == "r":
        return sys.stdin
    else:
        return sys.stdout
# Referências antecipadas são úteis quando 
# queremos referenciar a uma classe antes
# de ela ser definida

# Isso irá falhar na execução, "A" não foi definido
def f(foo: A) -> int: ...
    ...

class A:
    ...

# Se usarmos a string literal "A", o código irá passar
# desde que tenha uma classe com o nome A mais
# à frente no código
def f(foo: "A") -> int: # Sem erro
    ...

Decorators

Funções Decorators podem ser expressadas via generics. Veja a página Declaring decorators (Inglês) para maiores detalhes.

from typing import Any, Callable, TypeVar

F = TypeVar("F", bound=Callable[..., Any])

def bare_decorator(func: F) -> F:
    ...

def decorator_args(url: str) -> Callable[[F], F]:
    ...

Fonte

Como dito no início do post, o conteúdo original está disponível na documentação do mypy e no repositório oficial deles no GitHub sob a licença MIT, que nos permite traduzir o seu conteúdo sem restrições.

Esperamos que o conteúdo tenha agradado. Caso tenha dúvidas, críticas ou queira sugerir outros conteúdos em Inglês para serem traduzidos, bata um papo com a gente nos comentários!

#NoBrains #NoGains 🧠

0 Shares:
2 comentários
Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Você também pode gostar