Introduzido desde o Python 3.5, o typing módulo do Python tenta fornecer uma maneira de sugerir tipos para ajudar verificadores de tipo estático e linters a prever erros de forma precisa.
Devido ao Python ter que determinar o tipo de objetos durante a execução, às vezes fica muito difícil para os desenvolvedores descobrir o que exatamente está acontecendo no código.
Mesmo verificadores de tipo externos como o PyCharm IDE não produzem os melhores resultados; em média, apenas prevendo corretamente erros cerca de 50% do tempo, de acordo com esta resposta no StackOverflow.
O Python tenta mitigar este problema introduzindo o que é conhecido como dica de tipo (anotação de tipo) para ajudar os verificadores de tipo externos a identificar quaisquer erros. Esta é uma boa maneira para o programador sugerir o tipo do(s) objeto(s) sendo usado, durante o próprio tempo de compilação e garantir que os verificadores de tipo funcionem corretamente.
Isso torna o código Python muito mais legível e robusto também, para outros leitores!
NOTA: Isso não faz a verificação de tipo real no momento da compilação. Se o objeto real retornado não fosse do mesmo tipo que sugerido, não haverá nenhum erro de compilação. É por isso que usamos verificadores de tipo externos, como o mypy, para identificar quaisquer erros de tipo.
Pré-requisitos Recomendados
Para usar o módulo typing
de forma eficaz, é recomendado que você utilize um verificador/linter de tipos externo para verificar a correspondência de tipos estáticos. Um dos verificadores de tipo mais amplamente utilizados para Python é o mypy, então recomendo que você o instale antes de ler o restante do artigo.
Já cobrimos os conceitos básicos de verificação de tipos em Python. Você pode revisar este artigo primeiro.
Vamos utilizar o mypy
como verificador de tipo estático neste artigo, que pode ser instalado por meio de:
pip3 install mypy
Você pode executar o mypy
em qualquer arquivo Python para verificar se os tipos correspondem. Isso é como se estivesse ‘compilando’ o código Python.
mypy program.py
Após depurar os erros, você pode executar o programa normalmente usando:
python program.py
Agora que cobrimos nossos pré-requisitos, vamos tentar usar algumas das funcionalidades do módulo.
Dicas de Tipos / Anotações de Tipos
Nas funções
Podemos anotar uma função para especificar o tipo de retorno e os tipos de seus parâmetros.
def print_list(a: list) -> None:
print(a)
Isso informa ao verificador de tipos (mypy
no meu caso) que temos uma função print_list()
, que receberá uma list
como argumento e retornará None
.
def print_list(a: list) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
Vamos rodar isso primeiro no nosso verificador de tipos mypy
:
vijay@JournalDev:~ $ mypy printlist.py
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)
Como esperado, obtemos um erro; já que a linha #5 tem o argumento como um int
, em vez de uma list
.
Nas Variáveis
Desde o Python 3.6, também podemos anotar os tipos de variáveis, mencionando o tipo. Mas isso não é obrigatório se você quiser que o tipo de uma variável mude antes que a função retorne.
# Anota 'raio' como sendo um float
radius: float = 1.5
# Podemos anotar uma variável sem atribuir um valor!
sample: int
# Anota 'área' para retornar um float
def area(r: float) -> float:
return 3.1415 * r * r
print(area(radius))
# Imprime todas as anotações da função usando
# o dicionário '__annotations__'
print('Dictionary of Annotations for area():', area.__annotations__)
Resultado do mypy:
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
Esta é a maneira recomendada de usar o mypy
, fornecendo primeiro as anotações de tipo antes de usar o verificador de tipo.
Aliases de Tipo
O módulo typing
nos fornece Aliases de Tipo, que são definidos atribuindo um tipo ao alias.
from typing import List
# Vetor é uma lista de valores de ponto flutuante
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)
Saída
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]
No trecho acima, Vector
é um alias que representa uma lista de valores de ponto flutuante. Podemos sugerir um tipo em um alias, que é o que o programa acima está fazendo.
A lista completa de aliases aceitáveis é dada aqui.
Vamos dar uma olhada em mais um exemplo, que verifica cada par chave:valor em um dicionário e verifica se eles correspondem ao formato nome:email.
from typing import Dict
import re
# Criar um alias chamado 'ContactDict'
ContactDict = Dict[str, str]
def check_if_valid(contacts: ContactDict) -> bool:
for name, email in contacts.items():
# Verificar se nome e email são strings
if (not isinstance(name, str)) or (not isinstance(email, str)):
return False
# Verificar o email [email protected]
if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
return False
return True
print(check_if_valid({'vijay': '[email protected]'}))
print(check_if_valid({'vijay': '[email protected]', 123: '[email protected]'}))
Saída do mypy
vijay@JournalDev:~ $ mypy validcontacts.py
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)
Aqui, obtemos um erro estático de compilação em tempo de execução no mypy
, já que o parâmetro name
em nosso segundo dicionário é um número inteiro (123). Assim, os aliases são outra maneira de garantir uma verificação precisa de tipo pelo mypy
.
Crie tipos de dados definidos pelo usuário usando NewType()
Podemos usar a função NewType()
para criar novos tipos de dados definidos pelo usuário.
from typing import NewType
# Crie um novo tipo de usuário chamado 'StudentID' que consiste em
# um inteiro
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)
O verificador de tipo estático tratará o novo tipo como se fosse uma subclasse do tipo original. Isso é útil para detectar erros lógicos.
from typing import NewType
# Crie um novo tipo de usuário chamado 'StudentID'
StudentID = NewType('StudentID', int)
def get_student_name(stud_id: StudentID) -> str:
return str(input(f'Enter username for ID #{stud_id}:\n'))
stud_a = get_student_name(StudentID(100))
print(stud_a)
# Isso está incorreto!!
stud_b = get_student_name(-1)
print(stud_b)
Saída do mypy
vijay@JournalDev:~ $ mypy studentnames.py
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)
O tipo Any
Este é um tipo especial, informando ao verificador de tipo estático (mypy
no meu caso) que todo tipo é compatível com essa palavra-chave.
Considere nossa antiga função print_list()
, agora aceitando argumentos de qualquer tipo.
from typing import Any
def print_list(a: Any) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
Agora, não haverá erros quando executarmos o mypy
.
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1
Todas as funções sem um tipo de retorno ou tipos de parâmetros usarão implicitamente Any
.
def foo(bar):
return bar
# Um verificador de tipo estático tratará o acima
# como tendo a mesma assinatura que:
def foo(bar: Any) -> Any:
return bar
Você pode, portanto, usar Any para misturar código tipado estaticamente e dinamicamente.
Conclusão
Neste artigo, aprendemos sobre o módulo typing do Python, que é muito útil no contexto de verificação de tipo, permitindo que verificadores de tipo externos, como mypy
, relatem com precisão quaisquer erros.
Isso nos fornece uma maneira de escrever código tipado estaticamente em Python, que é uma linguagem tipada dinamicamente por design!
Referências
- Documentação do Python para o módulo de tipagem (Isso contém detalhes extensivos sobre mais métodos neste módulo, e eu recomendo isso como uma referência secundária)
- Pergunta no StackOverflow sobre dicas de tipo (Isso fornece uma discussão muito boa sobre o tópico. Eu recomendo muito que você leia este tópico também!)
Source:
https://www.digitalocean.com/community/tutorials/python-typing-module