args e *kwargs: funções com argumentos variáveis
1. Introdução aos argumentos variáveis
Em Python, ao definir funções, frequentemente nos deparamos com situações onde o número de argumentos não é conhecido antecipadamente. Imagine criar uma função que calcula a média de notas: o professor pode ter 5 alunos em uma turma e 40 em outra. Com parâmetros fixos, seriam necessárias múltiplas definições ou soluções improvisadas.
A linguagem Python oferece uma solução elegante através dos conceitos de empacotamento (packing) e desempacotamento (unpacking) de sequências. Esses mecanismos permitem que uma função receba um número variável de argumentos, tornando o código mais flexível e reutilizável.
Os operadores *args e **kwargs são as ferramentas que possibilitam essa flexibilidade. Eles permitem capturar argumentos posicionais extras em uma tupla e argumentos nomeados extras em um dicionário, respectivamente.
2. *args: argumentos posicionais variáveis
O *args é utilizado na definição de uma função para indicar que ela pode receber um número variável de argumentos posicionais. Dentro da função, args se comporta como uma tupla contendo todos os argumentos extras fornecidos.
def soma(*args):
total = 0
for numero in args:
total += numero
return total
print(soma(1, 2, 3)) # 6
print(soma(10, 20, 30, 40)) # 100
print(soma()) # 0 (args vazio)
Observe que podemos chamar soma() com qualquer quantidade de números, incluindo nenhum. A tupla args será vazia nesse último caso.
3. **kwargs: argumentos nomeados variáveis
Enquanto *args lida com argumentos posicionais, **kwargs captura argumentos nomeados (também chamados de keyword arguments). Dentro da função, kwargs é um dicionário onde as chaves são os nomes dos parâmetros e os valores são os argumentos fornecidos.
def configurar(**kwargs):
for chave, valor in kwargs.items():
print(f"{chave} = {valor}")
configurar(host="localhost", porta=8080, debug=True)
# Saída:
# host = localhost
# porta = 8080
# debug = True
Este padrão é extremamente útil para funções que aceitam um conjunto flexível de opções de configuração.
4. Combinando args e *kwargs com parâmetros fixos
É possível combinar parâmetros fixos, *args e **kwargs em uma mesma função, desde que respeitada a ordem obrigatória:
- Parâmetros posicionais comuns
*args(argumentos posicionais variáveis)- Parâmetros nomeados (com valor padrão)
**kwargs(argumentos nomeados variáveis)
def func(a, b, *args, opcao=True, **kwargs):
print(f"a = {a}, b = {b}")
print(f"args = {args}")
print(f"opcao = {opcao}")
print(f"kwargs = {kwargs}")
func(1, 2, 3, 4, 5, opcao=False, verbose=True, modo="teste")
# Saída:
# a = 1, b = 2
# args = (3, 4, 5)
# opcao = False
# kwargs = {'verbose': True, 'modo': 'teste'}
Um caso especial interessante é o uso do * sozinho, que força todos os parâmetros seguintes a serem nomeados:
def conectar(host, port, *, timeout=30, ssl=True):
print(f"Conectando a {host}:{port} (timeout={timeout}, ssl={ssl})")
conectar("localhost", 8080, timeout=60) # Válido
# conectar("localhost", 8080, 60) # Erro! timeout deve ser nomeado
5. Desempacotamento de argumentos com * e **
Os operadores * e ** também podem ser usados no momento da chamada da função para desempacotar sequências e dicionários, respectivamente. Este é o mecanismo inverso do empacotamento feito por *args e **kwargs.
def apresentar(nome, idade, cidade):
print(f"{nome} tem {idade} anos e mora em {cidade}")
# Desempacotamento de lista/tupla
dados_pessoa = ["Ana", 28, "São Paulo"]
apresentar(*dados_pessoa) # Equivalente a apresentar("Ana", 28, "São Paulo")
# Desempacotamento de dicionário
dados_dict = {"nome": "Carlos", "idade": 35, "cidade": "Rio de Janeiro"}
apresentar(**dados_dict) # Equivalente a apresentar(nome="Carlos", idade=35, cidade="Rio de Janeiro")
Esta técnica é frequentemente usada para combinar múltiplas fontes de dados ou para delegar chamadas entre funções.
6. Boas práticas e casos de uso comuns
Quando usar *args:
- Funções matemáticas que operam sobre coleções (soma, média, produto)
- Decoradores que precisam envolver funções com assinaturas desconhecidas
- Funções de logging que aceitam qualquer número de valores
def log(mensagem, *valores):
print(f"[LOG] {mensagem}: {', '.join(str(v) for v in valores)}")
log("Valores processados", 10, 20, 30)
Quando usar **kwargs:
- Configurações de bibliotecas e frameworks
- Wrappers que repassam argumentos para funções internas
- Herança e chamadas a super() em classes
class Base:
def __init__(self, **kwargs):
self.config = kwargs
class Derivada(Base):
def __init__(self, nome, **kwargs):
super().__init__(**kwargs)
self.nome = nome
Nomenclatura: Embora args e kwargs sejam apenas convenções (poderíamos usar *valores ou **opcoes), segui-las torna o código mais legível para outros programadores Python.
Cuidados: O uso excessivo de **kwargs pode dificultar a manutenção, pois esconde quais parâmetros são realmente esperados. Documente adequadamente ou considere usar dicionários tipados com TypedDict em projetos maiores.
7. Erros comuns e como evitá-los
Esquecer de desempacotar ao repassar argumentos:
def externa(*args):
# ERRADO: passa a tupla inteira como um único argumento
# interna(args)
# CORRETO: desempacota a tupla
interna(*args)
def interna(a, b, c):
return a + b + c
Conflito entre *args e parâmetros nomeados padrão:
def problema(*args, opcao="padrao"):
pass
# Chamada ambígua: 3 é argumento posicional ou nomeado?
# problema(3, opcao="teste") # Funciona: 3 vai para args
# Para evitar confusão, use o operador * sozinho:
def solucao(*, opcao="padrao"):
pass
Diferença entre vazio: Uma função pode ser chamada sem argumentos posicionais (*args vazio) ou sem argumentos nomeados (**kwargs vazio). Ambos são casos válidos e devem ser tratados adequadamente.
8. Exemplo integrado: aplicação prática
Vamos construir um decorador genérico que mede o tempo de execução de qualquer função, usando *args e **kwargs para aceitar qualquer assinatura:
import time
from functools import wraps
def temporizador(func):
@wraps(func)
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = func(*args, **kwargs)
fim = time.time()
print(f"{func.__name__} executou em {fim - inicio:.4f} segundos")
return resultado
return wrapper
@temporizador
def calcular_media(*notas):
return sum(notas) / len(notas) if notas else 0
@temporizador
def saudacao(nome, **opcoes):
saudacao_base = f"Olá, {nome}!"
if opcoes.get("formal"):
return saudacao_base.upper()
return saudacao_base
print(calcular_media(7, 8, 9, 10)) # Média: 8.5 + tempo de execução
print(saudacao("Maria", formal=True)) # OLÁ, MARIA! + tempo de execução
Este exemplo demonstra como *args e **kwargs são ferramentas essenciais no Python para criar código flexível, reutilizável e elegante. Eles permitem que funções e decoradores se adaptem a diferentes contextos sem sacrificar a clareza ou a funcionalidade.
Referências
- Documentação oficial: Definição de funções — Explicação completa sobre parâmetros especiais, incluindo args e *kwargs na documentação oficial do Python.
- Real Python: args and *kwargs in Python — Tutorial detalhado com exemplos práticos e casos de uso avançados.
- Python.org: Mais sobre definição de funções — Seção da documentação que aborda empacotamento e desempacotamento de argumentos.
- GeeksforGeeks: args and *kwargs in Python — Guia completo com exemplos variados e explicações sobre o uso combinado.
- Programiz: Python args and *kwargs — Tutorial interativo com exemplos de código e exercícios práticos para fixação do conteúdo.