Documentando código Python com Type Hints
Tags: python
Published: 2021-07-08 13:13:21


## 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:
1. É 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.

2. É uma linguagem de tipagem dinâmica. Isso significa que podemos alterar o tipo da variável ao longo do programa, como por exemplo:
```py
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:


| Tipo  | Descrição      | Exemplo  |
| :---- | :------------- | :------  |
| str   | Texto          | 'hello'  |
| int   | Número Inteiro | 1 , 2, 3 |
| float | Número Real    | 1.0, 2.0 |
| list  | Lista          | [1,2,3]  |
| dict  | Dicionário     | {'title': 'post 1', 'category': 1 } |


### 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:**
```py
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](https://www.python.org/dev/peps/pep-0257/), com a chegada do Docstring, é apresentada uma convenção para documentar nossas funções. Abaixo um exemplo com a tentativa de facilitar nosso entendimento:
```py
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](https://www.python.org/dev/peps/pep-0318/), 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:
```py
@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://pythonacademy.com.br/blog/domine-decorators-em-python)
    
-   [https://realpython.com/primer-on-python-decorators/](https://realpython.com/primer-on-python-decorators/)
    

  
  
## Finalmente os *Annotations*
No ano de 2006, com a chegada do Python 3, a [PEP-3107](https://www.python.org/dev/peps/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](https://www.python.org/dev/peps/pep-0483/) 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](https://www.python.org/dev/peps/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](https://www.python.org/dev/peps/pep-0484/) 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:
```py
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:
```py
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](http://mypy-lang.org/), 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:

  
```py
def validar_palavras(palavras: list[str]) -> bool:
	return True if 'teste' in palavras else False

print(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!`

  
```py
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:
**![Mensagem de tipo inesperado para o status do usuário](https://lh6.googleusercontent.com/gxPc-oaNy5QMJe7lC4BfubTDxbn1_0NlyMiD6Z8DLgwNptfLORbg1GniykxD3H3Zyds9jTGDXEhqcrRl55kMnzKC_Xmr36FEvq0sp8fAAIaBWByPDZtWpiVrJhFRCEUgSAm6lJFj)**

Você poderá usar também a opção do autocomplete apertando ctrl+espaço:
**![Sugestão de autocomplete para o usuário](https://lh5.googleusercontent.com/P7Bjsifc_CddFdXRdEHHRUfOc1tJQXP1uIbZ58U64YFQ8nCh76kHNy7H5y64U8yanc9htujsmD5RLWaJk5GnRluXwkcth56EDVh-O9PFwoQf2bTlH9kepstzODAa9I7r8Sf4epHQ)**

**Para conhecer mais como definir os tipos com classes poderá consultar a [PEP-589](https://www.python.org/dev/peps/pep-0589/).**

## 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