DEV Community 👩‍💻👨‍💻

Cover image for Documentando código Python com Type Hints
Flávio Filipe
Flávio Filipe

Posted on • Updated on

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:

  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:

n = 2 # n é do tipo inteiro
n = teste # n é do tipo string
Enter fullscreen mode Exit fullscreen mode

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:

def somar(a, b):
    return a+b
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Aqui temos alguns exemplos da utilização dos decorators para facilitar o entendimento deles:

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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 False

print(validate_words(['Nome', 'teste'])
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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

Você poderá usar também a opção do autocomplete apertando ctrl+espaço:
Sugestão de autocomplete para o usuário

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

Top comments (0)

🌚 Life is too short to browse without dark mode