Argumentos de funções
Nesta aula, exploramos os diferentes tipos de argumentos em funções Python: posicionais, nomeados, valores padrão, *args, **kwargs e argumentos keyword-only. Compreender essas variações é essencial para criar funções flexíveis e legíveis.
Funções são blocos fundamentais na programação Python, e a forma como passamos argumentos para elas influencia diretamente a flexibilidade e clareza do código. Nesta aula, vamos mergulhar nos diferentes mecanismos de passagem de argumentos: posicionais, nomeados, valores padrão, uso de *args e **kwargs, e argumentos keyword-only. Dominar esses conceitos permite escrever funções que se adaptam a diversas situações sem sacrificar a legibilidade.
Vamos começar com os fundamentos e, em seguida, explorar padrões mais avançados que tornam as funções Python tão poderosas. Ao final, você será capaz de projetar interfaces de função claras e robustas.
Posicionais e nomeados
Em Python, os argumentos podem ser passados de duas formas principais: posicionais (por ordem) ou nomeados (por nome). Argumentos posicionais são aqueles cujo valor é atribuído com base na posição em que são fornecidos na chamada da função. Já os argumentos nomeados são passados explicitamente com o nome do parâmetro, independentemente da ordem.
Considere uma função simples que calcula a potência de um número:
def potencia(base, expoente):
return base ** expoente
# Chamada posicional
print(potencia(2, 3)) # 8
# Chamada nomeada
print(potencia(expoente=3, base=2)) # 8
Na chamada posicional, 2 é atribuído a base e 3 a expoente pela ordem. Na chamada nomeada, a ordem não importa, pois os valores são vinculados pelos nomes dos parâmetros. É possível misturar ambos, mas os argumentos posicionais devem vir antes dos nomeados:
print(potencia(2, expoente=3)) # Válido
# print(potencia(base=2, 3)) # Inválido! SyntaxError
Valores padrão
Parâmetros com valores padrão permitem que a função seja chamada sem fornecer todos os argumentos. O valor padrão é usado se o argumento correspondente não for passado. Por exemplo:
def saudacao(nome, saudacao="Olá"):
return f"{saudacao}, {nome}!"
print(saudacao("João")) # Olá, João!
print(saudacao("Maria", "Oi")) # Oi, Maria!
Atenção: valores padrão são avaliados apenas uma vez, no momento da definição da função. Isso é importante quando o valor padrão é mutável, como listas ou dicionários. Exemplo clássico de problema:
def adiciona_item(item, lista=[]):
lista.append(item)
return lista
print(adiciona_item(1)) # [1]
print(adiciona_item(2)) # [1, 2] # Surpresa! A lista padrão foi modificada
Para evitar esse comportamento, use None como valor padrão e crie uma nova lista dentro da função:
def adiciona_item(item, lista=None):
if lista is None:
lista = []
lista.append(item)
return lista
*args e **kwargs
Às vezes, queremos que uma função aceite um número variável de argumentos. O *args permite passar um número arbitrário de argumentos posicionais, que são empacotados em uma tupla. Já **kwargs captura argumentos nomeados extras em um dicionário. Exemplo:
def print_args(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
print_args(1, 2, 3, nome="Ana", idade=30)
# args: (1, 2, 3)
# kwargs: {'nome': 'Ana', 'idade': 30}
Os nomes args e kwargs são convenções; você pode usar qualquer nome, mas o asterisco único (*) e duplo (**) são a parte essencial. Esses parâmetros devem vir após todos os parâmetros posicionais e antes dos keyword-only (se houver).
Um uso comum é delegar argumentos para outra função:
def chamar_com_log(func, *args, **kwargs):
print(f"Chamando {func.__name__} com args={args} e kwargs={kwargs}")
return func(*args, **kwargs)
print(chamar_com_log(potencia, 2, 3)) # Chamando potencia com args=(2,3) e kwargs={} -> 8
Argumentos keyword-only
Argumentos keyword-only são aqueles que só podem ser passados por nome (não posicionalmente). Eles são definidos após um * solitário na lista de parâmetros. Por exemplo:
def configurar(host, port, *, timeout=30, verbose=False):
print(f"Conectando a {host}:{port} (timeout={timeout}, verbose={verbose})")
# Válido:
configurar("localhost", 8080, timeout=10, verbose=True)
# Inválido:
# configurar("localhost", 8080, 10, True) # TypeError: takes 2 positional arguments but 4 were given
Os parâmetros após o * são keyword-only. Eles podem ter valores padrão ou não. Se não tiverem, devem ser fornecidos obrigatoriamente por nome. Isso torna a chamada mais explícita e evita erros de posição.
Você também pode combinar *args com keyword-only. Nesse caso, os keyword-only vêm depois de *args:
def func(a, b, *args, c, d=10):
print(a, b, args, c, d)
func(1, 2, 3, 4, c=5) # 1 2 (3,4) 5 10
Observe que c é obrigatório e deve ser passado por nome. Isso é útil para APIs onde alguns parâmetros são essenciais e devem ser explicitados.
Boas práticas
- Prefira argumentos nomeados para parâmetros booleanos ou opcionais, aumentando a legibilidade.
- Evite valores padrão mutáveis; use
Nonee crie novos objetos dentro da função. - Use
*argse**kwargsprincipalmente em funções wrapper ou quando a interface precisa ser flexível, mas documente bem os parâmetros esperados. - Argumentos keyword-only são excelentes para parâmetros de configuração que não devem ser confundidos com dados posicionais.
- Mantenha a ordem correta: parâmetros posicionais,
*args, keyword-only (com ou sem padrão),**kwargs.
Referências
- Python Tutorial: More on Defining Functions
- Python Language Reference: Function Definitions
- Real Python: Python args and kwargs Demystified
- PEP 3102 – Keyword-Only Arguments
- Python FAQ: Why are default values shared between objects?
Exercícios
-
Crie uma função chamada
calcularque aceite dois números e uma operação (string) como argumento posicional, e a operação deve ter valor padrão"soma". A função deve retornar o resultado da operação (soma, subtração, multiplicação, divisão).✓ Resposta:def calcular(a, b, operacao="soma"): if operacao == "soma": return a + b elif operacao == "subtracao": return a - b elif operacao == "multiplicacao": return a * b elif operacao == "divisao": if b != 0: return a / b else: return "Erro: divisão por zero" else: return "Operação inválida" # Teste print(calcular(10, 5)) # 15 print(calcular(10, 5, "multiplicacao")) # 50 -
Escreva uma função
concatenarque aceite uma string separadora e um número arbitrário de strings via*args, e retorne a concatenação de todas as strings usando o separador.✓ Resposta:def concatenar(separador, *args): return separador.join(args) print(concatenar("-", "a", "b", "c")) # a-b-c print(concatenar(", ", "Python", "Java", "C")) # Python, Java, C -
Crie uma função
criar_perfilque aceitenomeeidadecomo posicionais, e depois aceite argumentos nomeados arbitrários (**kwargs) representando outros dados (ex: cidade, profissao). A função deve retornar um dicionário com todos os dados.✓ Resposta:def criar_perfil(nome, idade, **kwargs): perfil = {"nome": nome, "idade": idade} perfil.update(kwargs) return perfil print(criar_perfil("Ana", 30, cidade="São Paulo", profissao="Engenheira")) # {'nome': 'Ana', 'idade': 30, 'cidade': 'São Paulo', 'profissao': 'Engenheira'} -
Defina uma função
conectarque exija dois argumentos posicionais (host,port) e tenha dois argumentos keyword-only:timeout(padrão 30) essl(padrão False). A função deve imprimir uma mensagem com os parâmetros.✓ Resposta:def conectar(host, port, *, timeout=30, ssl=False): print(f"Conectando a {host}:{port} (timeout={timeout}, ssl={ssl})") conectar("localhost", 8080) conectar("example.com", 443, timeout=10, ssl=True) # Tentativa inválida: conectar("localhost", 8080, 20) # TypeError -
Escreva uma função
decorarque aceite uma função como argumento e retorne uma nova função que imprime "Antes" antes de chamar a função original e "Depois" depois. Use*argse**kwargspara repassar argumentos.✓ Resposta:def decorar(func): def wrapper(*args, **kwargs): print("Antes") resultado = func(*args, **kwargs) print("Depois") return resultado return wrapper @decorar def saudacao(nome): print(f"Olá, {nome}!") saudacao("João") # Antes # Olá, João! # Depois