Construindo uma Biblioteca Python do Zero — Parte 2

Vinícius Martins
5 min readOct 12, 2023

--

Aprenda a estruturar seus arquivos Python em módulos usando __init__, decorators e docstrings e muito mais, para criar sua própria biblioteca Python elegante e fácil de usar.

Photo by AltumCode on Unsplash

Este artigo é a sequência de outro artigo e recomendo que leiam.

Acesse aqui a parte 1.

O repositório com o código pode ser visto neste link.

Neste tópico vamos ver como conseguimos inserir validações nas nossas funções usando decorators, além de inserir mensagens de erros personalizadas.

Inserindo Validações Através de Decorators

Inicialmente vamos já criar a estrutura de diretórios para essa parte. Ela irá fica da seguinte forma:

.
├── mathematics
| ├── __init__.py
│ └── geometric
│ └── _pitagoras.py
│ └── utils
| ├── __init__.py
│ └── _params_validation.py
└── main.py

Criamos a pasta utils onde ficará as funções utilitárias do nosso pacote. Agora vamos colocar as validações dentro do arquivo _params_validation.py. Vamos realizar duas validações, a primeira é verificando se os valores dos argumentos a e b são números e a segunda verificando se o argumento calc é “hip” ou “calc”, pois é o que é aceito pela função.

Esse seria um código para realizar a validação dos argumento a e b:

def __num_validations(*args, **kwargs):
a = kwargs.get('a', args[0] if args else None)
b = kwargs.get('b', args[1] if len(args) > 1 else None)

variables_verification = (isinstance(a, (int, float)) and isinstance(b, (int, float)))
msg = 'Definir um valor numérico para as variáveis "a" e "b"'
assert variables_verification, msg

Se você nunca viu algumas funcionalidades desse código, não se preocupe, vou repassar linha a linha pra você entender:

  • Começando pela linha em que definimos a função, temos a sintaxe *args e **kwargs. O que é isso? Não vou me estender muito, mas resumindo, *args retorna uma lista do python com todos os argumentos passados na função que não são nomeados e **kwargs retorna um dicionário do python com os argumentos passados na função que são nomeados. Por exemplo: se chamarmos a função pitagoras dessa forma: Pitagoras(3, 4), estamos passando argumentos não nomeados, ou seja, *args retornará uma lista assim: [3, 4] e kwargs retorna vazio. Agora, se chamarmos a função dessa forma Pitagoras(a=3, b=4), estamos passando argumentos nomeados, então **kwargs retornará um dicionário {a: 3, b: 4}.
  • Em seguida, usamos o método get do dicionário. Ele é importante pois não conseguimos saber se o usuário irá passar os argumentos nomeados ou não, então, ele nos permite tentar retornar o argumento nomeado em kwargs e caso não encontre, ele retorna o indíce desse argumento que está na lista args.
  • Realizamos a verificação se as variáveis a e b são do tipo int ou float, retornando True ou False.
  • Por fim, escrevo a mensagem de erro personalizada e uso o assert do python que, caso a verificação das variáveis retorne False, ele interrompe o programa e exibe a mensagem de erro.

Mas o código acima só valida os argumentos a e b, vamos fazer um código para validar o argumento calc também na mesma lógica do anterior:

def __calc_validation(*args, **kwargs):
calc = kwargs.get('calc', args[2] if len(args) > 2 else 'hip')
calc_verification = (calc in ('hip', 'cat'))

msg = f"Valor '{calc}' para parâmetro Calc não é válido.\\nOs valores possíveis para o parâmetro Calc são: 'hip' (para cálculo da hipotenusa) e 'cat' (para cálculo do cateto)."
assert calc_verification, msg

Agora que temos as validações, podemos escrever o código mágico para disponibilizar esse código como decorator. Para isso, podemos fazer o seguinte:

def __pitagoras_validations(func):

def wrapper(*args, **kwargs):

__num_validations(*args, **kwargs)
__calc_validation(*args, **kwargs)

return func(*args, **kwargs)

return wrapper

Neste código, a função __pitagoras_validations recebe o argumento func, que é a nossa função Pitagoras, com todos os seus argumentos. Em seguida, a função wrapper recebe esses argumentos e repassa para as funções de validação. Por fim a função Pitagoras é retornada para ser executada.

Porém, fazendo dessa maneira ocorre um problema, ao tentarmos chamar a função, não aparece a docstring que inserimos.

Para consertar isso, vamos importar a biblioteca functools do python e adicionar como o decorator a função @functools.wraps, que trata esse problema pra gente:

import functools

def __pitagoras_validations(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):

__num_validations(*args, **kwargs)
__calc_validation(*args, **kwargs)

return func(*args, **kwargs)

return wrapper

Nossa função de validação está pronta. precisamos disponibiliza-la para ser usada. Para isso, no arquivo __init__.py da pasta utils, disponibilizamos a função __pitagoras_validations, expondo ela na lista especial __all__ para ser utilizada:

#__init__.py
from ._validate_params import __pitagoras_validations

__all__ = [
'__pitagoras_validations'
]

Por fim, no arquivo onde está localizada a função Pitagoras (_pitagoras.py), importamos a validação e inserimos o decorator acima da definição da função.

import numpy as np
from ..utils import __pitagoras_validations

@__pitagoras_validations
def Pitagoras(a, b, calc='hip') -> float:

Desta forma, a validação já está funcionando. Veja que o erro obtido é a mensagem que personalizamos:

Erro ao passar string ao invés de números:

Erro ao passar argumento calc diferente de “hip” ou “cat”

Agora sua biblioteca personalizada começa a tomar forma. Essa é uma estrutura básica para começar a deixar seus projetos mais organizados e fáceis de utilizar, além do fato de o desenvolvimento de novas funcionalidades se tornar bem mais escalável. Pense que, quando quisermos inserir uma nova função, basta criar um novo arquivo que contenha a função, inseri-lo na pasta e disponibiliza-la no arquivo __init__.py.

Uma melhoria que poderíamos realizar são nas validações, para que possamos reutiliza-las em mais métodos, de forma que não seja necessário escrever novas validações para cada função, a menos que seja realmente necessário.

Agora que você sabe como é a estrutura básica, tenta explorar os repositórios no github de bibliotecas que você usa como pandas ou scikit-learn. Como desafio, ache uma função que você sempre utiliza, navegando através dos arquivos __init__ e veja as validações que são feitas, como ela é disponibilizada, a docstring da função e etc. Não se preocupe em entender os detalhes de implementação, apenas tente entender a estrutura. É um ótimo exercício de aprendizado explorar e absorver códigos como esses.

Espero que tenham gostado, até a próxima!

--

--

Vinícius Martins

Profissional em Análise de Dados e Negócios. Especializando em Ciência de Dados e Algoritimos de IA.