Python *args e **kwargs

Você já reparou que o print aceita quantos valores você quiser? print("a"), print("a", "b"), print("a", "b", "c", "d"). As funções que você escreveu até aqui não fazem isso: cada uma tem um número fixo de parâmetros. Nesta aula você vai criar funções que recebem quantos argumentos vierem: uma soma de quantos números a Ana quiser, um pedido com quantos itens o cliente pedir, uma ficha com quantos campos forem precisos.

Você vai aprender
  • A receber qualquer quantidade de argumentos posicionais com *args (eles chegam numa tupla).
  • A receber qualquer quantidade de argumentos nomeados com **kwargs (eles chegam num dicionário).
  • A misturar parâmetros normais, *args e **kwargs, e em que ordem.
  • O caminho inverso: abrir uma lista na chamada com * e um dicionário com **.
  • Quando usar *args e quando é melhor passar uma lista.
  • Os erros reais: a lista inteira caindo no *args, o parâmetro "engolido", a chave que não veio.

A ideia: a sacola e a ficha com campos extras

No caixa da padaria, a Ana não sabe quantas coisas cada cliente vai levar. Por isso ela não usa uma bandeja com três buracos (um pão, um leite, um café); ela usa uma sacola, onde cabe o que vier: nada, um item, vinte itens. O *args é a sacola: um parâmetro só que recolhe todos os argumentos posicionais que sobrarem.

Já no cadastro de clientes, todo mundo tem nome, mas cada um tem informações a mais diferentes: um deu o telefone, outro o aniversário, outro a cidade. A ficha tem um espaço "outras informações", onde cada coisa vai com uma etiqueta ("telefone: …"). O **kwargs é esse espaço: recolhe todos os argumentos nomeados que sobrarem, cada um com o seu nome.

*args**kwargs
RecolheArgumentos posicionais extras: f(1, 2, 3)Argumentos nomeados extras: f(a=1, b=2)
Chegam comoUma tupla: (1, 2, 3)Um dicionário: {'a': 1, 'b': 2}
ComparaçãoA sacolaAs "outras informações" da ficha, com etiquetas
Nomearguments, "argumentos"keyword arguments, "argumentos nomeados"

1. *args: quantos valores vierem

Primeiro, veja o que chega no parâmetro com *:

def mostrar(*numeros):
    print(numeros)


mostrar()
mostrar(5)
mostrar(5, 10, 20)
$ python3 ver_args.py
()
(5,)
(5, 10, 20)
*
O asterisco antes do nome do parâmetro quer dizer: "junte aqui todos os argumentos posicionais que vierem".
numeros
O nome do parâmetro. Escolha um nome que diga o que vem dentro (numeros, itens, notas). Em exemplos genéricos, o costume é *args; o que importa é o *, e não a palavra "args".
()
Sem argumentos, chega uma tupla vazia (aula Tuplas). Não é erro.
(5,)
Uma tupla com um item só. A vírgula no fim é o jeito de o Python mostrar que é uma tupla, e não um 5 entre parênteses.
(5, 10, 20)
Os três argumentos, na ordem em que vieram.

Como é uma tupla, dá para percorrê-la com for. Uma soma de quantos números a Ana quiser:

def somar(*numeros):
    total = 0
    for n in numeros:
        total = total + n
    return total


print(somar(5, 10))
print(somar(5, 10, 20, 30))
print(somar())
$ python3 somar.py
15
65
0

Parâmetros normais antes do *args

O *args pode vir depois de parâmetros comuns. Os primeiros argumentos preenchem os parâmetros comuns, e o resto cai na sacola:

def pedido(cliente, *itens):
    print(f"Pedido de {cliente} (itens: {len(itens)})")
    for item in itens:
        print(f"  - {item}")


pedido("Ana", "pão", "leite", "café")
pedido("Bruno", "bolo")
$ python3 pedido.py
Pedido de Ana (itens: 3)
  - pão
  - leite
  - café
Pedido de Bruno (itens: 1)
  - bolo

Em pedido("Ana", "pão", "leite", "café"), o "Ana" foi para cliente e os outros três foram juntos para itens.

Missão: a função media só aceita duas notas, e a segunda chamada dá erro. Troque os dois parâmetros por um *notas, para que ela calcule a média de quantas notas vierem. (Dica: sum(notas) soma tudo e len(notas) conta quantas são.)

def media(nota1, nota2):
    return (nota1 + nota2) / 2


print(media(8, 9))
print(media(7, 8, 9, 10))
def media(*notas):
    return sum(notas) / len(notas)


print(media(8, 9))
print(media(7, 8, 9, 10))

2. **kwargs: quantos nomes vierem

Com dois asteriscos, o parâmetro recolhe os argumentos nomeados (aula Valores Padrão e Argumentos Nomeados):

def ficha(**dados):
    print(dados)


ficha(nome="Ana", cidade="Teresina")
ficha()
$ python3 ver_kwargs.py
{'nome': 'Ana', 'cidade': 'Teresina'}
{}

Chegou um dicionário (aula Dicionários): cada nome virou uma chave ('nome', 'cidade') e cada valor ficou ligado à sua chave. Sem argumentos, chega um dicionário vazio, {}. Os itens ficam na ordem em que foram escritos na chamada.

Na ficha de clientes, o nome é obrigatório e o resto é livre:

def ficha(nome, **dados):
    print(f"Cliente: {nome}")
    for campo, valor in dados.items():
        print(f"  {campo}: {valor}")


ficha("Ana", cidade="Teresina", telefone="(86) 99999-0000")
ficha("Bruno", aniversario="12/03")
$ python3 ficha.py
Cliente: Ana
  cidade: Teresina
  telefone: (86) 99999-0000
Cliente: Bruno
  aniversario: 12/03
**dados
Os dois asteriscos: "junte aqui todos os argumentos nomeados que sobrarem". Em exemplos genéricos, o costume é **kwargs.
dados.items()
Percorre o dicionário de par em par: a chave em campo e o valor em valor (aula Percorrendo um Dicionário).

3. Tudo junto, e a ordem

def pedido(cliente, *itens, **opcoes):
    print("cliente:", cliente)
    print("itens:", itens)
    print("opcoes:", opcoes)


pedido("Ana", "pão", "café", entrega=True, troco=50)
$ python3 tudo.py
cliente: Ana
itens: ('pão', 'café')
opcoes: {'entrega': True, 'troco': 50}

Cada argumento encontrou o seu lugar: o primeiro posicional foi para cliente, os outros posicionais para a tupla itens, e os nomeados para o dicionário opcoes. A ordem no def é sempre esta:

  1. Parâmetros comuns (cliente), com ou sem valor padrão.
  2. *args (a sacola).
  3. Parâmetros que só podem ser passados com nome (veja o cenário "parâmetro engolido" em Quando dá errado).
  4. **kwargs, sempre por último.

4. O caminho inverso: abrindo a lista na chamada

O asterisco também funciona na chamada, ao contrário: ele abre uma lista (ou tupla) e entrega cada item como um argumento separado. Dois asteriscos abrem um dicionário em argumentos nomeados:

def somar(*numeros):
    total = 0
    for n in numeros:
        total = total + n
    return total


def ficha(nome, cidade):
    print(f"{nome} mora em {cidade}.")


notas = [8, 9, 7]
print(somar(*notas))
dados = {"nome": "Ana", "cidade": "Teresina"}
ficha(**dados)
compras = ["arroz", "feijão", "café"]
print(compras)
print(*compras)
print(*compras, sep=", ")
$ python3 desempacotar.py
24
Ana mora em Teresina.
['arroz', 'feijão', 'café']
arroz feijão café
arroz, feijão, café
ChamadaÉ o mesmo que
somar(*notas)somar(8, 9, 7)
ficha(**dados)ficha(nome="Ana", cidade="Teresina")
print(compras)Mostra a lista, com colchetes e aspas
print(*compras)print("arroz", "feijão", "café"): três valores, separados por espaço

Repare: o * no def junta (vários valores viram uma tupla); o * na chamada espalha (uma lista vira vários valores).


Formas diferentes de receber vários valores

Uma função pode receber vários números de dois jeitos: com *args ou com um parâmetro comum que recebe uma lista inteira.

def somar_lista(numeros):
    total = 0
    for n in numeros:
        total = total + n
    return total


def somar(*numeros):
    total = 0
    for n in numeros:
        total = total + n
    return total


notas = [8, 9, 7]
print(somar_lista(notas))
print(somar(8, 9, 7))
print(somar(*notas))
$ python3 lista_param.py
24
24
24
FormaQuando usar
Parâmetro que recebe uma lista: somar_lista(notas)Quando os valores já estão numa lista (vieram de um arquivo, de um laço). É o mais comum nos programas
*args: somar(8, 9, 7)Quando quem chama vai escrever os valores um por um, na hora, como no print
**kwargsQuando a função aceita "informações extras" de nomes variados, que ela só repassa ou mostra
Parâmetros fixos com nomeQuando você sabe exatamente o que a função precisa. Prefira sempre que puder: os nomes documentam a função, e erros de digitação viram TypeError na hora

Quando dá errado

Mandar a lista inteira para o *args
def somar(*numeros):
    total = 0
    for n in numeros:
        total = total + n
    return total


notas = [8, 9, 7]
print(somar(notas))
$ python3 lista_inteira.py
Traceback (most recent call last):
  File "/home/ana/lista_inteira.py", line 9, in <module>
    print(somar(notas))
          ~~~~~^^^^^^^
  File "/home/ana/lista_inteira.py", line 4, in somar
    total = total + n
            ~~~~~~^~~
TypeError: unsupported operand type(s) for +: 'int' and 'list'

Leia o caminho: a linha 9 chamou somar, e dentro dela, na linha 4, a conta falhou. "Tipos não suportados para o +: int e list". Por quê? A lista foi um argumento só, então a sacola ficou com um item: numeros virou ([8, 9, 7],). Na primeira volta do laço, n era a lista inteira, e o Python tentou fazer 0 + [8, 9, 7]. Como consertar: abra a lista na chamada, somar(*notas), ou escreva uma função que recebe a lista num parâmetro comum.

Valor sem nome para o **kwargs
def ficha(**dados):
    print(dados)


ficha("Ana")
$ python3 posicional_kwargs.py
Traceback (most recent call last):
  File "/home/ana/posicional_kwargs.py", line 5, in <module>
    ficha("Ana")
    ~~~~~^^^^^^^
TypeError: ficha() takes 0 positional arguments but 1 was given

O **dados só recolhe argumentos com nome; ele não tem onde guardar um valor sem etiqueta. Para o Python, a função não aceita nenhum argumento posicional. Como consertar: ficha(nome="Ana"), ou acrescente um parâmetro comum: def ficha(nome, **dados):.

O parâmetro engolido pela sacola
def pedido(*itens, cliente):
    print(f"{cliente} pediu {itens}")


pedido("pão", "leite", "Ana")
$ python3 depois_args.py
Traceback (most recent call last):
  File "/home/ana/depois_args.py", line 5, in <module>
    pedido("pão", "leite", "Ana")
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
TypeError: pedido() missing 1 required keyword-only argument: 'cliente'

"Falta 1 argumento obrigatório só-nomeado: 'cliente'". A sacola *itens pegou todos os posicionais, inclusive o "Ana", e não sobrou nada para o cliente. Todo parâmetro que vem depois do *args só pode ser preenchido com nome (por isso keyword-only, "só por nome"). Como consertar: passe com nome, ou coloque o parâmetro antes do *itens:

def pedido(*itens, cliente):
    print(f"{cliente} pediu {itens}")


pedido("pão", "leite", cliente="Ana")
$ python3 depois_args_ok.py
Ana pediu ('pão', 'leite')
**kwargs antes do *args
def pedido(**opcoes, *itens):
    print(itens, opcoes)
$ python3 ordem_def.py
  File "/home/ana/ordem_def.py", line 1
    def pedido(**opcoes, *itens):
                         ^
SyntaxError: arguments cannot follow var-keyword argument

"Nada pode vir depois do argumento de nomes variáveis" (var-keyword é o nome técnico do **kwargs). Como consertar: o **kwargs é sempre o último: def pedido(*itens, **opcoes):.

Nome que não pode ser nome
def ficha(**dados):
    print(dados)


ficha(cor-favorita="azul")
$ python3 nome_invalido.py
  File "/home/ana/nome_invalido.py", line 5
    ficha(cor-favorita="azul")
          ^^^^^^^^^^^^^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

Os nomes dos argumentos nomeados seguem as regras dos nomes de variáveis (aula Nomes de Variáveis). Com o hífen, o Python leu cor - favorita, uma subtração, e achou estranho um = depois de uma conta. A sugestão "==" não ajuda aqui. Como consertar: cor_favorita="azul".

Ler uma chave que não veio
def ficha(nome, **dados):
    print(f"Cliente: {nome}")
    print(f"Telefone: {dados['telefone']}")


ficha("Bruno", cidade="Picos")
$ python3 chave_falta.py
Cliente: Bruno
Traceback (most recent call last):
  File "/home/ana/chave_falta.py", line 6, in <module>
    ficha("Bruno", cidade="Picos")
    ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ana/chave_falta.py", line 3, in ficha
    print(f"Telefone: {dados['telefone']}")
                       ~~~~~^^^^^^^^^^^^
KeyError: 'telefone'

KeyError: 'telefone': "erro de chave": o dicionário dados não tem a chave 'telefone', porque o Bruno não informou. Com **kwargs, nada garante que uma chave veio. Como consertar: use o get, que devolve um valor reserva quando a chave não existe:

def ficha(nome, **dados):
    print(f"Cliente: {nome}")
    print(f"Telefone: {dados.get('telefone', 'não informado')}")


ficha("Bruno", cidade="Picos")
$ python3 chave_get.py
Cliente: Bruno
Telefone: não informado

Se a função precisa do telefone, o melhor é torná-lo um parâmetro comum: aí, se faltar, o Python avisa na hora com um TypeError claro.


Referência rápida

EscritaO que faz
def f(*args):Junta os argumentos posicionais numa tupla
def f(**kwargs):Junta os argumentos nomeados num dicionário
def f(a, *args, b, **kwargs):A ordem: comuns, *args, só-nomeados, **kwargs
f(*lista)Abre a lista: cada item vira um argumento posicional
f(**dicionario)Abre o dicionário: cada chave vira um argumento nomeado
print(*lista, sep=", ")Mostra os itens da lista sem colchetes
kwargs.get("chave", reserva)Lê uma chave que talvez não tenha vindo
MensagemCausa provável
unsupported operand type(s) for +: 'int' and 'list'A lista foi inteira para o *args: use f(*lista)
takes 0 positional arguments but 1 was givenValor sem nome para uma função que só tem **kwargs
missing 1 required keyword-only argumentParâmetro depois do *args, passado sem nome
arguments cannot follow var-keyword argumentAlgo depois do **kwargs no def
KeyError: 'x'A chave não veio no **kwargs: use .get

Exercícios

1. A sacola

Complete o símbolo que faz o parâmetro recolher quantos itens vierem:

def cesta(frutas):
    print(frutas)

2. As etiquetas

Complete o símbolo que faz o parâmetro recolher os argumentos nomeados:

def cadastro(dados):
    print(dados)

3. Com def mostrar(*numeros): print(numeros), o que aparece em mostrar(3, 4)?

4. Que tipo de valor chega no parâmetro **opcoes?

5. A lista notas = [8, 9, 7] precisa ir para a função somar(*numeros). Qual chamada funciona?

Missão final: a etiqueta com detalhes

Missão: a função etiqueta não aceita os detalhes do produto, e a chamada dá erro. Acrescente um **detalhes e mostre, embaixo do nome, cada detalhe no formato preco: R$ 18,00.

def etiqueta(produto):
    print(f"== {produto} ==")


etiqueta("Bolo de milho", preco="R$ 18,00", peso="800 g")
def etiqueta(produto, **detalhes):
    print(f"== {produto} ==")
    for nome, valor in detalhes.items():
        print(f"{nome}: {valor}")


etiqueta("Bolo de milho", preco="R$ 18,00", peso="800 g")

Depois, acrescente validade="5 dias" na chamada e rode de novo: a etiqueta ganha uma linha, sem mudar nada na função.


Resumo

  • *args no def junta os argumentos posicionais extras numa tupla; pode chegar vazia.
  • **kwargs junta os argumentos nomeados extras num dicionário; leia as chaves com .get quando elas podem faltar.
  • O que importa são os asteriscos; os nomes args e kwargs são só costume. Prefira nomes que digam o conteúdo.
  • Ordem no def: comuns, *args, só-nomeados, **kwargs. Depois do *args, só com nome.
  • Na chamada, o efeito é o inverso: f(*lista) espalha a lista em argumentos, f(**dicionario) espalha o dicionário em argumentos nomeados.
  • Se você sabe exatamente o que a função precisa, parâmetros com nome são mais claros que *args/**kwargs.