Curso de Python / Coletar e desempacotar argumentos com *args e **kwargs

Na definição de uma função, *args reúne argumentos posicionais adicionais numa tupla e **kwargs reúne argumentos nomeados adicionais num dicionário. Numa chamada, * e ** fazem o caminho inverso: desempacotam uma coleção para fornecer argumentos separados à função.

Distinguir coleta na definição e desempacotamento na chamada

args e kwargs são nomes convencionais, não palavras reservadas. A sintaxe com asteriscos determina a função de cada parâmetro; você pode escolher um nome mais específico, como *amounts, para representar vários valores numéricos.

Local Expressão Comportamento
Definição def f(*args) Reúne os argumentos posicionais adicionais numa tupla.
Definição def f(**kwargs) Reúne os argumentos nomeados adicionais num dicionário.
Chamada f(*values) Fornece cada item do iterável como um argumento posicional.
Chamada f(**options) Fornece as entradas de um mapeamento com chaves de texto como argumentos nomeados.

“Adicionais” significa que os argumentos ainda não associados aos parâmetros comuns são recolhidos por esses parâmetros variáveis. No exemplo deste artigo, total() não tem um parâmetro posicional comum: todos os argumentos posicionais entram em amounts. O parâmetro fee, escrito depois de *amounts, só pode ser fornecido por nome e tem padrão zero. Esse comportamento se conecta às regras de argumentos posicionais, nomeados e valores padrão.

Executar uma soma variável e descrever opções

Salve o programa completo em varargs.py, com codificação UTF-8, numa pasta de prática. No terminal com o ambiente virtual do projeto selecionado, execute python varargs.py. Em macOS ou Ubuntu sem ambiente virtual, use python3 varargs.py quando esse for o nome do interpretador. Não há pacotes externos para instalar.

def total(*amounts, fee=0):
    return sum(amounts) + fee

def describe(**fields):
    return ", ".join(f"{key}={value}" for key, value in sorted(fields.items()))

values = [3500, 8000, 2500]
print(total(*values, fee=500))
options = {"category": "food", "currency": "KRW"}
print(describe(**options))

A saída esperada é:

14500
category=food, currency=KRW

total(*values, fee=500) fornece 3500, 8000 e 2500 como três argumentos posicionais. Eles formam a tupla amounts; a soma é 14000 e a taxa adicional eleva o resultado para 14500. Já describe(**options) transforma as entradas do dicionário em argumentos nomeados, que são recolhidos por fields.

Dentro de describe(), sorted(fields.items()) ordena as entradas por chave. A expressão produz trechos como category=food, e join() os une com vírgula e espaço. Por isso a saída segue a ordem alfabética das chaves, mesmo se a ordem de inserção em options for invertida. Os textos indicam categoria “alimentação” e moeda KRW; são rótulos e não provocam conversão monetária ou outra ação externa.

Reconhecer duplicações e expansões não pretendidas

As chaves de um mapeamento expandido com ** numa chamada precisam ser strings. A função de destino também precisa aceitar esses nomes, seja como parâmetros explícitos, seja por um parâmetro **kwargs. Como describe() aceita qualquer nome, um erro de digitação como curreny não gera uma exceção: ele aparece na descrição. Aceitar nomes não equivale a validar seu significado.

A chamada describe(category="food", **{"category": "travel"}) causa TypeError, pois a mesma chave foi fornecida duas vezes, mesmo com **fields na definição. Isso difere da criação de um dicionário como {**{"category": "food"}, **{"category": "travel"}}, em que o último valor substitui o anterior. Ao receber um erro de duplicação, confira tanto os nomes explícitos como os mapeamentos expandidos.

Também é necessário distinguir uma coleção de seus itens. total(values) fornece uma lista como um único argumento; neste programa, sum(amounts) tentaria somar essa lista a um número e causaria TypeError. total(*"123") fornece os textos "1", "2" e "3", sem convertê-los em números, e também falha. Desempacotar um dicionário com apenas * fornece suas chaves, não seus valores.

Uma função intermediária, ou wrapper, pode receber *args e **kwargs e encaminhar a chamada com func(*args, **kwargs). Passar kwargs sem os dois asteriscos enviaria o dicionário inteiro como um argumento posicional. O destino continua aplicando suas próprias regras; o encaminhamento não corrige nomes desconhecidos, tipos inadequados ou parâmetros duplicados. A referência de chamadas de funções detalha essas verificações.

Testar limites e manter a interface compreensível

  1. Acrescente somente 0 ao fim de values. Execute novamente: a soma com a taxa continua sendo 14500. Restaure a lista original antes do teste seguinte.
  2. Na primeira impressão, troque a chamada por total(*values). Sem fee=500, aplica-se o padrão zero e a soma passa a 14000. Restaure a taxa explícita.
  3. Troque temporariamente a primeira chamada por total(). Uma tupla de valores vazia tem soma zero, portanto o resultado é 0. Com total(fee=500), o resultado seria 500: a função não exige pelo menos um valor.
  4. Teste separadamente a duplicação de category mostrada acima e confirme TypeError. Retire a chamada inválida, restaure o programa e compare novamente as duas linhas de saída originais.

Argumentos variáveis são úteis quando a quantidade de entradas faz parte do problema. Se a função sempre recebe uma coleção com um significado único, um parâmetro como amounts que recebe a própria lista pode tornar a interface mais explícita. Da mesma forma, nomes fixos para opções obrigatórias permitem identificar omissões e erros mais cedo.

Ao encaminhar opções para uma API externa, aceite somente os nomes permitidos e verifique os campos obrigatórios e os valores antes do envio. Em operações de banco de dados, desempacotar argumentos não substitui consultas parametrizadas. O mecanismo de coleta e expansão é descrito no tutorial de listas de argumentos arbitrárias e desempacotamento; a validação do contrato continua sendo responsabilidade do programa.