Curso de Python / Argumentos posicionais, nomeados e valores padrão

Em Python, argumentos posicionais são associados pela ordem e argumentos nomeados são associados pelo nome do parâmetro. Valores padrão tornam entradas opcionais, mas uma lista usada como padrão pode conservar alterações entre chamadas; quando cada chamada precisa de uma lista nova, receba None e crie a lista no corpo da função.

Escolher chamadas por posição ou por nome

Na definição format_expense(name, amount, *, currency="KRW"), os dois primeiros parâmetros são obrigatórios e aceitam argumentos por posição ou por nome. Por posição, o primeiro valor corresponde a name e o segundo a amount. Com nomes explícitos, a ordem desses dois argumentos pode mudar sem alterar sua associação.

O asterisco isolado * na definição indica que os parâmetros seguintes são somente nomeados: currency precisa ser fornecido como currency="KRW" quando você quiser especificá-lo. O valor padrão permite omiti-lo, mas não permite passá-lo como terceiro argumento posicional. Esse asterisco não cria uma lista nem recebe um valor do chamador.

Uma chamada comum pode combinar argumentos posicionais primeiro e nomeados depois. Atribuir o mesmo parâmetro duas vezes causa TypeError; colocar um argumento posicional comum depois de um nomeado causa SyntaxError. Essas regras completam a distinção entre parâmetros, argumentos e valores retornados por funções.

Executar argumentos opcionais e listas novas

Salve o código completo em arguments.py, com codificação UTF-8, numa pasta de prática. No terminal com o ambiente virtual selecionado, execute python arguments.py. Em macOS ou Ubuntu sem ambiente virtual, use python3 arguments.py se esse for o nome do interpretador. Nenhuma biblioteca externa é necessária.

def format_expense(name, amount, *, currency="KRW"):
    return f"{name}: {amount:,} {currency}"

def append_name(name, names=None):
    if names is None:
        names = []
    names.append(name)
    return names

print(format_expense("lunch", 8000))
print(format_expense(amount=9000, name="dinner", currency="KRW"))
print(append_name("A"))
print(append_name("B"))

O resultado esperado é:

lunch: 8,000 KRW
dinner: 9,000 KRW
['A']
['B']

A primeira chamada usa a moeda padrão; a segunda explicita os nomes e muda a ordem de amount e name. As palavras lunch e dinner são apenas dados de exemplo, correspondentes a almoço e jantar. KRW identifica o won sul-coreano. Mudar esse texto para BRL mudaria somente o rótulo: a função não consulta cotação, não converte valores e não aplica formatação monetária brasileira.

O especificador :, usa vírgula para separar milhares, por isso aparece 8,000. Cada chamada de append_name() sem uma lista recebe o padrão None, cria uma lista vazia e acrescenta o nome. Assim, a chamada com B não reutiliza a lista criada na chamada com A.

Comparar um padrão compartilhado com uma lista criada por chamada

Uma expressão de valor padrão é avaliada quando a instrução de definição da função é executada, e não novamente a cada chamada. Usar names=[] cria um objeto que permanece como padrão daquela função. Como append() modifica esse objeto, chamadas posteriores que omitem a lista observam os nomes acrescentados antes.

Para comparar, salve este segundo programa completo em defaults.py e execute python defaults.py no mesmo terminal. Use o nome python3 quando necessário, como no primeiro programa. As chamadas precisam ocorrer na mesma execução para revelar o compartilhamento.

def shared(name, names=[]):
    names.append(name)
    return names
def fresh(name, names=None):
    if names is None:
        names = []
    names.append(name)
    return names
print(shared("A"))
print(shared("B"))
print(fresh("A"))
print(fresh("B"))

A saída esperada é:

['A']
['A', 'B']
['A']
['B']

A segunda chamada de shared() ainda encontra A e acrescenta B. Já fresh() cria listas diferentes para as duas chamadas que omitem names. Ao executar o arquivo inteiro novamente num processo novo, as definições também são executadas novamente: a primeira saída de shared() volta a ser ['A']. O acúmulo pertence ao objeto usado naquela execução, não a um arquivo de dados salvo automaticamente.

Use if names is None quando quiser preservar uma lista vazia passada explicitamente. Uma condição como if not names também considera a lista vazia falsa e criaria outra lista, perdendo a associação pretendida com o objeto do chamador. A documentação dos valores padrão de argumentos apresenta a razão para usar esse padrão com None.

Testar chamadas inválidas e a alteração de objetos fornecidos

  1. Depois das definições em arguments.py, acrescente temporariamente print(format_expense("lunch", 8000, "KRW")). O terceiro argumento posicional causa TypeError. Corrija a chamada para print(format_expense("lunch", 8000, currency="KRW")) e confira lunch: 8,000 KRW.
  2. Teste separadamente format_expense("lunch", 8000, amount=9000). O parâmetro amount receberia dois valores, portanto ocorre TypeError. Teste também format_expense(name="lunch", 8000): essa forma é sintaticamente inválida e impede a execução do arquivo inteiro. Remova cada teste inválido antes de seguir.
  3. Depois da definição de append_name(), acrescente names = [], result = append_name("A", names) e print(names, result is names), cada instrução em sua própria linha. A saída adicional deve ser ['A'] True: o objeto do chamador foi modificado e retornado.
  4. Somente para comparar, substitua if names is None por if not names e repita o teste anterior num processo novo. A saída adicional muda para [] False, pois a lista vazia fornecida foi substituída por uma nova. Restaure if names is None, retire as linhas de teste e confirme a saída original dos dois programas.

Evitar um padrão mutável compartilhado não significa copiar todas as listas recebidas. Quando o chamador passa uma lista, append_name() altera deliberadamente esse objeto. Se o contrato exigir preservar a entrada, é preciso decidir e implementar uma cópia; isso é uma escolha diferente da criação de uma lista quando o argumento foi omitido.

O mesmo momento de avaliação vale para uma expressão que calcula a data ou a hora atual: usá-la diretamente como padrão conserva o resultado obtido na definição. Se precisar de um valor novo em cada chamada, calcule-o no corpo quando receber o marcador escolhido. None é adequado quando significa “não fornecido”; se ele próprio for um argumento com significado, escolha outro marcador distinto. A documentação dos parâmetros especiais detalha como restringir a interface de chamada sem confundir essas regras com o conteúdo dos objetos recebidos.