Python Valores Padrão e Argumentos Nomeados

Na aula Parâmetros e Argumentos, cada parâmetro era obrigatório: faltou um argumento, TypeError. Mas na vida real muita coisa tem um "de sempre". Quem pede "um pão de queijo" não precisa dizer "quantidade: um". Nesta aula, os parâmetros ganham um valor padrão, usado quando a chamada não diz nada, e as chamadas ganham nomes: pedido("Café", quantidade=2), que deixam claro o que é cada valor.

Você vai aprender
  • A dar um valor padrão a um parâmetro (quantidade=1), tornando-o opcional.
  • A chamar a função com argumentos nomeados (nome=valor), em qualquer ordem.
  • A pular um parâmetro opcional e preencher só o seguinte.
  • Que o sep e o end do print são exatamente isso.
  • A armadilha da lista como valor padrão, e o jeito certo com None.
  • As regras de ordem e os erros reais quando elas são quebradas.

A ideia: "o de sempre"

O Seu Joaquim chega à padaria e diz só: "um café". A atendente já sabe o resto: copo médio, com açúcar. É o padrão. Se um dia ele quiser diferente, ele avisa: "um café, sem açúcar". Ele não precisa repetir tudo; diz só o que muda, e diz pelo nome ("sem açúcar"), sem precisar seguir uma ordem.

Na padariaNo Python
O café "de sempre": médio, com açúcarUm parâmetro com valor padrão: def cafe(tamanho="médio", acucar=True):
"Um café" (não diz mais nada)cafe(): vale o padrão
"Um café, sem açúcar"Um argumento nomeado: cafe(acucar=False)

1. O valor padrão

def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido("Bolo de milho")
pedido("Pão francês", 10)
$ python3 pedido.py
1 x Bolo de milho
10 x Pão francês
produto
Parâmetro obrigatório, como na aula anterior: toda chamada tem de preenchê-lo.
quantidade=1
Parâmetro com valor padrão (também se diz "opcional"). Se a chamada não mandar nada para ele, o Python usa o 1.
pedido("Bolo de milho")
Só um argumento: produto = "Bolo de milho" e quantidade fica com o padrão, 1.
pedido("Pão francês", 10)
Dois argumentos: o 10 substitui o padrão.

O valor padrão também funciona com texto:

def saudar(nome, saudacao="Bom dia"):
    print(f"{saudacao}, {nome}!")


saudar("Ana")
saudar("Bruno", "Boa noite")
$ python3 saudar.py
Bom dia, Ana!
Boa noite, Bruno!
Sem espaços em volta do =

Nas variáveis, o costume é x = 1, com espaços. No valor padrão (e no argumento nomeado, logo adiante), o costume do PEP 8 é sem espaços: quantidade=1. Com espaços também funciona; é só estilo. Assim, quem lê sabe na hora que aquele = está dentro dos parênteses de uma função.

Missão: a segunda chamada dá erro, porque a Ana não disse a quantidade do bolo. Rode, leia a mensagem e dê ao parâmetro quantidade o valor padrão 1 (mexa só no def).

def pedido(produto, quantidade):
    print(f"{quantidade} x {produto}")


pedido("Pão francês", 10)
pedido("Bolo de milho")
def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido("Pão francês", 10)
pedido("Bolo de milho")

2. Argumentos nomeados

Na chamada, você pode dizer a qual parâmetro vai cada valor, escrevendo nome_do_parametro=valor. Com nomes, a ordem não importa mais:

def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido(produto="Café", quantidade=2)
pedido(quantidade=3, produto="Pão de queijo")
pedido("Leite", quantidade=4)
$ python3 nomeados.py
2 x Café
3 x Pão de queijo
4 x Leite
  • Na primeira chamada, os dois argumentos têm nome.
  • Na segunda, estão na ordem "errada", e mesmo assim deu certo: o Python não olha a posição, olha o nome. Lembra do 30 tem Ana anos. da aula anterior? Com nomes, isso não acontece.
  • Na terceira, o primeiro vai pela posição e o segundo pelo nome. É o jeito mais comum: o que é óbvio vai pela posição, o que precisa de explicação vai com nome.

Em inglês, argumento nomeado é keyword argument ("argumento por palavra-chave"); o comum, pela posição, é positional argument. As mensagens de erro usam esses dois nomes.

Pulando um parâmetro opcional

O nome é a única saída quando há vários parâmetros opcionais e você quer mudar só o último:

def cartao(nome, mensagem="Parabéns", assinatura="Padaria Pão Quente"):
    print(f"{mensagem}, {nome}!")
    print(f"   Um abraço, {assinatura}")


cartao("Ana")
cartao("Bruno", "Feliz Natal")
cartao("Carla", assinatura="Tia Rosa")
$ python3 cartao.py
Parabéns, Ana!
   Um abraço, Padaria Pão Quente
Feliz Natal, Bruno!
   Um abraço, Padaria Pão Quente
Parabéns, Carla!
   Um abraço, Tia Rosa

No cartão da Carla, a mensagem ficou com o padrão ("Parabéns") e só a assinatura mudou. Pela posição, isso seria impossível: o segundo valor sempre cairia na mensagem.

Você já usou isso: sep e end

O print é uma função com parâmetros de valor padrão: sep=" " (o que vai entre os valores) e end="\n" (o que vai no fim, uma quebra de linha). Na aula As Opções sep e end, você os mudou com argumentos nomeados:

print("Ana", "Bruno", "Carla")
print("Ana", "Bruno", "Carla", sep=" - ")
print("sem pular linha", end="")
print(" (continuou na mesma linha)")
$ python3 print_sep.py
Ana Bruno Carla
Ana - Bruno - Carla
sem pular linha (continuou na mesma linha)

3. As regras de ordem

Duas regras, uma no def e uma na chamada:

OndeRegraCertoErrado
No defPrimeiro os obrigatórios, depois os que têm valor padrãodef pedido(produto, quantidade=1):def pedido(quantidade=1, produto):
Na chamadaPrimeiro os posicionais, depois os nomeadospedido("Café", quantidade=2)pedido(produto="Café", 2)

Faz sentido: se um parâmetro opcional viesse antes de um obrigatório, o Python não teria como saber se o primeiro valor da chamada é o opcional ou o obrigatório. As mensagens de quando essas regras são quebradas estão em Quando dá errado.

Para quando você ler a documentação: / e *

Em algumas funções você vai ver uma / ou um * sozinhos entre os parâmetros. Eles mudam a regra da chamada:

  • def pedido(produto, *, quantidade=1): — tudo o que vem depois do * só pode ser passado com nome. pedido("Café", quantidade=2) funciona; pedido("Café", 2) dá TypeError: pedido() takes 1 positional argument but 2 were given.
  • def dobro(numero, /): — tudo o que vem antes da / só pode ser passado pela posição. dobro(5) funciona; dobro(numero=5) dá TypeError: dobro() got some positional-only arguments passed as keyword arguments: 'numero'.

Muitas funções embutidas são assim: no help(len) do modo interativo, o len aparece como len(obj, /), e por isso len(obj=[1, 2]) não funciona (TypeError: len() takes no keyword arguments). Para os seus programas, por enquanto, você não precisa deles.


4. A armadilha: lista como valor padrão

A Ana quer uma função que comece uma lista de compras nova para cada pessoa. Parece razoável usar uma lista vazia como padrão:

def nova_lista(item, compras=[]):
    compras.append(item)
    return compras


ana = nova_lista("arroz")
bruno = nova_lista("café")
print("Ana:", ana)
print("Bruno:", bruno)
$ python3 mutavel.py
Ana: ['arroz', 'café']                             « o café do Bruno foi parar na lista da Ana
Bruno: ['arroz', 'café']

As duas pessoas ficaram com a mesma lista. O arroz da Ana apareceu na lista do Bruno, e o café do Bruno na da Ana. Nenhum erro, só um resultado errado, daqueles que dão muito trabalho para achar.

Por que acontece

O valor padrão é criado uma vez só: no momento em que o Python passa pelo def, e não a cada chamada. A lista [] do padrão é sempre a mesma lista, guardada junto com a função. Cada chamada sem o segundo argumento usa essa mesma lista, e o append (aula Adicionando Itens) vai acumulando nela. Dá para ver isso acontecendo:

def nova_lista(item, compras=[]):
    print("A lista padrão antes:", compras)
    compras.append(item)
    return compras


nova_lista("arroz")
nova_lista("café")
nova_lista("feijão")
print(nova_lista.__defaults__)
$ python3 mutavel_por_que.py
A lista padrão antes: []
A lista padrão antes: ['arroz']
A lista padrão antes: ['arroz', 'café']
(['arroz', 'café', 'feijão'],)

A cada chamada, a lista "vazia" do padrão chega com os itens das chamadas anteriores. A última linha espia os valores padrão guardados na função (__defaults__): lá está a lista, com os três itens. Com números e textos isso não acontece, porque eles não podem ser modificados por dentro, como a lista pode com o append (aula Mutável × Imutável).

O jeito certo: None como padrão

def nova_lista(item, compras=None):
    if compras is None:
        compras = []
    compras.append(item)
    return compras


ana = nova_lista("arroz")
bruno = nova_lista("café")
print("Ana:", ana)
print("Bruno:", bruno)
print(nova_lista("feijão", ana))
$ python3 mutavel_none.py
Ana: ['arroz']
Bruno: ['café']
['arroz', 'feijão']
compras=None
O padrão agora é None, "nada" (aula O Valor None), que não pode ser modificado.
if compras is None:
"Se não mandaram lista nenhuma…" (o is é o jeito certo de comparar com None, aula is e in).
compras = []
…cria uma lista vazia nova, agora, dentro desta chamada. Cada chamada ganha a sua.
nova_lista("feijão", ana)
Quem quiser continuar uma lista existente ainda pode passá-la.
Regra prática

Nunca use uma lista ([]), um dicionário ({}) ou um conjunto (set()) como valor padrão. Use None e crie o valor novo dentro da função.


Formas diferentes de chamar a mesma função

Com def pedido(produto, quantidade=1):, as chamadas abaixo pedem 2 cafés:

ChamadaQuando usar
pedido("Café", 2)Quando a função é curta e a ordem é óbvia
pedido("Café", quantidade=2)O mais legível: o principal pela posição, o detalhe com nome. Prefira quando o valor sozinho não diz o que é (um 2, um True)
pedido(produto="Café", quantidade=2)Quando há muitos parâmetros e você quer deixar tudo explícito
pedido(quantidade=2, produto="Café")Funciona, mas seguir a ordem do def facilita a leitura

E quando o valor é o padrão (1 café), o mais simples é não escrever nada: pedido("Café").


Quando dá errado

Parâmetro com padrão antes de um obrigatório
def pedido(quantidade=1, produto):
    print(f"{quantidade} x {produto}")


pedido("Café")
$ python3 ordem_def.py
  File "/home/ana/ordem_def.py", line 1
    def pedido(quantidade=1, produto):
                             ^^^^^^^
SyntaxError: parameter without a default follows parameter with a default

"Parâmetro sem padrão vem depois de um parâmetro com padrão". O ^^^^^^^ aponta o produto, que é obrigatório e está depois do quantidade=1. Como é SyntaxError, o programa nem começa. Como consertar: obrigatórios primeiro: def pedido(produto, quantidade=1):.

Argumento pela posição depois de um nomeado
def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido(produto="Café", 2)
$ python3 posicional_depois.py
  File "/home/ana/posicional_depois.py", line 5
    pedido(produto="Café", 2)
                            ^
SyntaxError: positional argument follows keyword argument

"Argumento posicional vem depois de argumento nomeado". Depois que você começa a usar nomes na chamada, o Python não aceita mais valores soltos, porque não saberia em que posição encaixá-los. Como consertar: pedido("Café", 2) ou pedido(produto="Café", quantidade=2).

Nome de parâmetro que não existe
def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido("Café", quantidad=2)
$ python3 nome_errado.py
Traceback (most recent call last):
  File "/home/ana/nome_errado.py", line 5, in <module>
    pedido("Café", quantidad=2)
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^
TypeError: pedido() got an unexpected keyword argument 'quantidad'. Did you mean 'quantidade'?

"pedido() recebeu um argumento nomeado inesperado, 'quantidad'. Você quis dizer 'quantidade'?". O nome na chamada tem de ser exatamente o nome do parâmetro no def. Como consertar: quantidade=2.

O mesmo parâmetro duas vezes
def pedido(produto, quantidade=1):
    print(f"{quantidade} x {produto}")


pedido("Café", produto="Leite")
$ python3 repetido.py
Traceback (most recent call last):
  File "/home/ana/repetido.py", line 5, in <module>
    pedido("Café", produto="Leite")
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: pedido() got multiple values for argument 'produto'

"pedido() recebeu vários valores para o argumento 'produto'". O "Café", pela posição, já foi para o produto; depois veio produto="Leite" para o mesmo lugar. Como consertar: cada parâmetro recebe um valor só.

A lista como padrão, de novo

A seção 4 inteira é este erro: def f(x, lista=[]): não dá mensagem nenhuma, só mistura os dados de chamadas diferentes. Se uma lista "lembra" de coisas que não deveria, procure um =[] ou ={} num def.


Referência rápida

EscritaO que faz
def f(a, b=10):b tem valor padrão 10: a chamada pode omiti-lo
f(1)a = 1, b = 10 (o padrão)
f(1, 5)a = 1, b = 5 (substitui o padrão)
f(1, b=5) ou f(b=5, a=1)Argumentos nomeados: a ordem não importa
def f(x, lista=None): + if lista is None: lista = []O jeito certo de ter uma lista "vazia" como padrão
def f(a, *, b):b só pode ser passado com nome
def f(a, /):a só pode ser passado pela posição
MensagemCausa provável
parameter without a default follows parameter with a defaultNo def, obrigatório depois de opcional
positional argument follows keyword argumentNa chamada, valor solto depois de um nome=valor
got an unexpected keyword argument 'x'Nome de parâmetro digitado errado (ou que não existe)
got multiple values for argument 'x'O mesmo parâmetro recebeu pela posição e pelo nome
Uma lista "lembra" de chamadas anterioresLista como valor padrão: troque por None

Exercícios

1. O tamanho padrão

Complete para que o café seja "médio" quando ninguém disser o tamanho:

def cafe(tamanho):
    print(f"Café {tamanho}")

2. Só a assinatura

Com a função cartao(nome, mensagem="Parabéns", assinatura="Padaria Pão Quente"), complete a chamada para mudar só a assinatura:

cartao("Davi", ="Vó Lurdes")

3. Com def pedido(produto, quantidade=1):, o que mostra pedido(quantidade=5, produto="Sonho"), se o corpo é print(f"{quantidade} x {produto}")?

4. Qual destes def está certo?

5. Por que def f(item, lista=[]): é uma armadilha?

Missão final: uma lista para cada um

Missão: rode e veja: a Ana e o Bruno ficaram com a mesma lista de compras. Conserte a função para que cada um tenha a sua (use None como padrão).

def nova_lista(item, compras=[]):
    compras.append(item)
    return compras


ana = nova_lista("arroz")
bruno = nova_lista("café")
print("Ana:", ana)
print("Bruno:", bruno)
def nova_lista(item, compras=None):
    if compras is None:
        compras = []
    compras.append(item)
    return compras


ana = nova_lista("arroz")
bruno = nova_lista("café")
print("Ana:", ana)
print("Bruno:", bruno)

Resumo

  • def f(a, b=10): dá a b um valor padrão: se a chamada não disser nada, vale o 10.
  • Argumentos nomeados (f(a=1, b=5)) dizem a qual parâmetro vai cada valor; a ordem deixa de importar, e dá para pular parâmetros opcionais.
  • No def: obrigatórios antes dos opcionais. Na chamada: posicionais antes dos nomeados.
  • O sep e o end do print são parâmetros com valor padrão.
  • O valor padrão é criado uma vez, no def. Por isso nunca use lista ou dicionário como padrão: use None e crie o valor dentro da função.
  • * e / no def obrigam a passar com nome ou pela posição; você vai vê-los na documentação.