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:
    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
    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:
    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 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!
    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
    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
  • 22

    This website collects cookies to deliver better user experience

    Documentando código Python com Type Hints