Documentando código Python com Type Hints
Introdução
Fiquei um pouco confuso ao começar a estudar sobre a declaração explícita com Python. Ao tentar comparar com outras linguagens como Java, C++ ou Typescript a compreensão começa a ficar um pouco problemática. Para entender o que é, para que serve e quando utilizar é preciso entender o motivo da sua criação.
Para começar precisamos ressaltar duas coisas importantes sobre o Python:
- É uma linguagem fortemente tipada, isto é, cada variável tem um tipo e em alguns casos é preciso convertê-las para realizar operações, por exemplo:
‘1’ + 1
irá retornar uma exceção pois os tipos são diferentes. - É uma linguagem de tipagem dinâmica. Isso significa que podemos alterar o tipo da variável ao longo do programa, como por exemplo:
n = 2 # n é do tipo inteiro
n = ‘teste’ # n é do tipo string
Tipo de dados
Para conhecermos melhor os tipos em python podemos utilizar o método type(var)
.
Os mais comuns são:
Vantagens da Tipagem Estática
A grande vantagem de se trabalhar com linguagens de tipagem estática é que ao definir o seu tipo: garantimos que não teremos problemas em tentar somar um inteiro com uma string durante o desenvolvimento do código.
Outra vantagem é poder entender os tipos de dados que uma função está esperando, apontando um erro imediato na IDE durante a escrita do código caso o parâmetro passado seja de um tipo diferente.
Mas e se tratando de Python, como podemos ganhar agilidade no desenvolvimento e evitar erros comuns com os tipos de dados e deixar claro para quem for dar manutenção no futuro os tipos de dados que esperamos trabalhar?
Vamos considerar o código abaixo:
def somar(a, b):
return a+b
Olhando para ele fica difícil saber o real propósito da sua criação. Ele foi criado para somar números, textos ou listas? O que aconteceria se eu passasse um número no primeiro parâmetro e um texto no segundo? Precisamos deixar claro para quem precisar olhar para este código no futuro.
Docstring
Na PEP-257, com a chegada do Docstring, é apresentada uma convenção para documentar nossas funções. Abaixo um exemplo com a tentativa de facilitar nosso entendimento:
def somar(a, b):
'''
Retorna a soma de dois números inteiros
Params:
a (int)
b (int)
'''
return a+b
Dependendo da IDE ou editor de texto utilizado, ao chamar esta função e passar o mouse por cima, ele irá apresentar a sua documentação. Mas pensando no cenário onde teremos vários métodos e para todos eles precisaríamos criar um docstring somente para informar o tipo de parâmetro e seu retorno acaba se tornando uma tarefa repetitiva.
Decoradores para Funções e Métodos
Na PEP-318, com a chegada dos decoradores para as funções e métodos, é apresentado uma sintaxe para ser utilizada com a finalidade de definir os tipos de parâmetros e retorno, assim poderíamos simplificar nossa documentação criando uma funçao de para informar os parâmetros e retorno:
@accepts (int, int)
@returns (int)
def somar(a, b):
return a+b
Aqui temos alguns exemplos da utilização dos decorators para facilitar o entendimento deles:
- https://pythonacademy.com.br/blog/domine-decorators-em-python
- https://realpython.com/primer-on-python-decorators/
Finalmente os Annotations
No ano de 2006, com a chegada do Python 3, a PEP-3107 nos trouxe uma outra forma de documentar nosso código com as Anotações de Função. Agora nós podemos fazer as anotações dentro do parâmetro da função e logo em seguida informar seu retorno, que basicamente fazem o uso das anotations para fazer o mapeamento.
Type Hints
A PEP-483 nos trás uma nova “teoria” da proposta de implementação de tipos para o Python 3.5 Segue a citação que deixa bem claro o real propósito da nova implementação sugerida:
É importante que o usuário seja capaz de definir os tipos de uma forma que possa ser entendida por verificadores de tipo. O objetivo deste PEP é propor uma forma sistemática de definir tipos para anotações de tipo de variáveis e funções usando a sintaxe PEP 3107 . Essas anotações podem ser usadas para evitar muitos tipos de bugs, para fins de documentação, ou talvez até mesmo para aumentar a velocidade de execução do programa. Aqui, nos concentramos apenas em evitar bugs usando um verificador de tipo estático.
Porém é com a PEP-484 que isso acontece, com a chegada dos Type Hints ou Dicas de Tipo.
Segue um exemplo atualizado usando a nova sintaxe e falaremos sobre ele em seguida:
def somar(a: int, b: int) -> int:
return a+b
Agora, de uma forma mais semântica, conseguimos documentar melhor nosso método. Fica claro os tipos de dados de entrada e saída. Agora vamos à “confusão” citada no início deste artigo quando tentei comparar com outras linguagens.
Ao olharmos o histórico da implementação podemos perceber que esta funcionalidade está mais ligada a documentação e legibilidade do código ao invés de tornar a linguagem estática. Sendo assim, diferente de outras linguagens estáticas, ao fazer uma declaração explícita dos valores de entrada ele não nos obriga a informá-los ou proíbe a mudança de tipo ao longo do código. Isso significa que o código abaixo ainda é um código válido:
def somar(a: int, b: int) -> int:
return a+b print(somar('a', 'b')) >> 'ab'
Isso ocorre porque o intuito é DOCUMENTAR e não obrigar os tipos de entrada e saída. A grande vantagem desta funcionalidade está no desenvolvimento se combinado com a ferramenta de terceiros para fazer a validação no código.
Utilizando uma IDE ou um editor de texto configurado para o python, ao escrever o código acima seria apresentado um erro informando que os tipos de dados são incompatíveis. Outras ferramentas, como o MyPy, podem garantir se o código segue as regras de implementação propostas. Com isso em mente, vamos explorar um pouco mais o que podemos fazer com os Types Hints.
Eu estou utilizando o PyCharm, uma IDE feita para Python. Ela tem uma ótima integração com os Type Hints fazendo as validações em tempo de desenvolvimento. Mas sintam-se a vontade para testar em outros editores. Você poderá executar o MyPy sempre que quiser testar suas implementações
EXEMPLOS COM TYPE HINTS
Exemplo 1: Precisamos passar uma lista de palabras para um método fazer algum tipo de validação nelas. Assim, precisamos especificar o tipo de lista que estamos esperando:
def validar_palavras(palavras: list[str]) -> bool:
return True if 'teste' in palavras else Falseprint(validate_words(['Nome', 'teste'])
Utilizando o list[str]
informamos que estamos esperando uma lista de palavras para nossa função. No método acima o -> bool
informa que o retorno é um boleano (Verdadeiro ou Falso).
Exemplo 2: Precisamos dar as boas vindas ao suário. Por isso iremos criar um método que recebe o nome e o status do usuário, se ele tiver o status ATIVO irá retornar a mensagem: Bem vindo <NOME>!
. Mas se ele possuir o status SUSPENSO deverá retornar a mensagem: Usuário Suspenso!
from typing import TypedDict from enum import Enum
class UserStatus(Enum):
ATIVO = 1
SUSPENSO = 2
class UserType(TypedDict):
nome: str
status: UserStatus
def boas_vindas(usuario: UserType) -> str:
if usuario['status'] == UserStatus.SUSPENSO:
return 'Usuário Suspenso!' return f"Bem vindo {usuario['nome']}"
usuario_ativo: UserType = {'nome': 'Usuário Ativo', 'status': 'ativo'}
usuario_suspenso: UserType = {'nome': 'Usuário Suspenso', 'status': UserStatus.SUSPENSO}
print(boas_vindas(usuario_ativo))
print(boas_vindas(usuario_suspenso))
Para este exemplo nós criamos duas classes para ser a referência de tipo de entrada para nossa função. A class UserStatus
será usada para definir nossos tipos padrões de status aceitos, enquanto a UserType
serão os tipos de dados esperados pelo usuário.
Caso vc esteja utilizando o PyCharm ou algum editor configurado para reconhecer os tipos verá que poderá utilizar o autocomplete e caso passe algum parâmetro errado o próprio editor já acusa que há um erro:
Você poderá usar também a opção do autocomplete apertando ctrl+espaço:
Para conhecer mais como definir os tipos com classes poderá consultar a PEP-589.
Conclusão
Com a declaração explicita podemos:
- Documentar melhor nosso código
- Ter segurança na hora de fazer o refatoramento
- Aproveitar ao máximo o autocomplete
- Pensar melhor nos dados da nossa aplicação
- Deixar o código mais claro e limpo