[Dúvida] [Python] Como evitar a repetição de parâmetros em várias funções.

Em um código Python, se várias funções recebem os mesmos parâmetros (especialmente se forem muitos), existe alguma boa prática para evitar repetição e melhorar a organização?

Por exemplo nesse código:

def calcular_imposto(valor_bruto, aliquota, deducoes, dependentes, estado):

def calcular_salario_liquido(valor_bruto, aliquota, deducoes, dependentes, estado):

def gerar_holerite(valor_bruto, aliquota, deducoes, dependentes, estado):

Imagine uma dezena ou mais de funções assim, todas com muitos parametros e todos se repetindo por serem usados em todas as funções. Existe alguma forma de evitar a repetição?

Tem algumas opções como usar a lib dataclass para gerar um placeholder para esses valores, então tu conseguiria passar o placeholder ao invês de uma repetição.

from dataclasses import dataclass

@dataclass
class ParametrosFinanceiros:
    valor_bruto: float
    aliquota: float
    deducoes: float
    dependentes: int
    estado: str

def calcular_imposto(params: ParametrosFinanceiros):
    # use params.valor_bruto, params.aliquota, etc.
    pass

def calcular_salario_liquido(params: ParametrosFinanceiros):
    pass

def gerar_holerite(params: ParametrosFinanceiros):
    pass

# Uso
parametros = ParametrosFinanceiros(5000, 0.2, 300, 2, 'SP')
calcular_imposto(parametros)
calcular_salario_liquido(parametros)
gerar_holerite(parametros)

Sem modificar as funções, uma alternativa é colocar todos os valores em uma tupla, e depois usar a sintaxe de unpacking ao chamá-las:

params = (valor_bruto, aliquota, deducoes, dependentes, estado)
 
calcular_imposto(*params)
calcular_salario_liquido(*params)
gerar_holerite(*params)

Repare no asterisco antes de params, é ele que faz o unpacking: o primeiro elemento da tupla é passado como o primeiro argumento da função, o segundo elemento é passado como o segundo argumento e assim por diante.


Claro que tem outras soluções mais rebuscadas, como criar uma classe que contém todos os valores, aí as funções só recebem a instância da classe. Mas aí precisa avaliar se justifica aumentar a complexidade.

Por exemplo, se não puder mudar as funções, a solução com unpacking me parece mais interessante.

Quando os parâmetros se repetem demais, talvez é uma indicação de que eles pertencem ao escopo de uma classe, inclusive as funções.

Uma solução é a que foi dada pelo KitsuneSemCalda, mas você pode incluir os métodos também:

Assim, as chamadas não precisam mais repetir a expressão "parametros"

(PS: Mudei o nome da classe para CalculosFinanceiros, porque faz mais sentido nesse escopo)

from dataclasses import dataclass

@dataclass
class CalculosFinanceiros:
    valor_bruto: float
    aliquota: float
    deducoes: float
    dependentes: int
    estado: str

    def calcular_imposto(self):
        # use self.valor_bruto, self.aliquota, etc.
        pass
    
    def calcular_salario_liquido(self):
        pass
    
    def gerar_holerite(self):
        pass

# Uso
calculos = CalculosFinanceiros(5000, 0.2, 300, 2, 'SP')
calculos.calcular_imposto()
calculos.calcular_salario_liquido()
calculos.gerar_holerite()

Salve Andy!

Não conheço nenhum artifício da linguagem pra omitir esses parâmetros repetidos, mas também acredito que caso exista esse artifício isso só dificultaria a legibilidade do código.

Hoje em dia as IDEs possuem auto-complete, o que facilita bastante. Caso você queira reduzir seu código você pode passar como argumento um dicionário (não é uma boa opção e é mais trabalhoso na hora de chamar a função, mas solucionaria teu problema tendo que passar somente 1 variável)

def sum(nums):
    return (nums['a'] + nums['b'])
    
nums = {'a': 5, 'b': 7}

print(f'Sum of {nums["a"]} and {nums["b"]} is {sum(nums)}')
Passar dicionários como argumentos para resolver esse problema; ao meu ver é a melhor forma. Utilizar encapsulamento quando há um número alto ou padrões de dados conhecidos ou repetidos é uma boa prática de programação, isso proporciona um código mais fácil de ler, manutenível e menos propenso à erros, é algo que você aprende ao trabalhar com linguagens com paradigma de OO. Ao citar que passar um dicionário não é uma boa opção, fez você receber esses downvotes.
Obrigado pelo feedback lucieudo. Realmente não tinha pensado por esse lado!
Vale lembrar que, caso não seja possível alterar as funções, ainda dá pra passar um dicionário: ```python def calcular_imposto(valor_bruto, aliquota, deducoes, dependentes, estado): # faz algo com os valores... def calcular_salario_liquido(valor_bruto, aliquota, deducoes, dependentes, estado): # faz algo com os valores... # mais trocentas funções com os mesmos parâmetros... params = { 'valor_bruto': 10, 'aliquota': 20, 'deducoes': 3, 'dependentes': 4, 'estado': 'SP' } calcular_imposto(**params) calcular_salario_liquido(**params) ``` O que faz a "mágica" são os dois asteriscos antes de `params`: eles fazem o *unpacking* do dicionário. No exemplo acima, ele faz com que o parâmetro `valor_bruto` receba o valor `10`, `aliquota` receba o valor `20` e assim por diante. Claro que se vc já tiver os valores em variáveis separadas, talvez não compense criar o dicionário. Neste caso eu ia preferir usar uma das soluções acima: a [tupla que eu sugeri](/kht/6ac67ac0-b786-4cbf-bd7a-ec1dfbc5d1ca) (caso não possa mudar as funções) ou [uma classe](/KitsuneSemCalda/84296de3-de1e-4936-b099-706804581f0a) (mas aí teria que alterar todas as funções, e dependendo do caso pode valer a pena). De qualquer forma, esta é uma das possibilidades, mas acho que só seria "melhor" caso o dicionário já estivesse criado (por exemplo, se ele veio de algum lugar que já retorna um JSON com todos os valores).