Blog

args e kwargs em Python

Uma estrela na frente de um parâmetro junta os argumentos posicionais que sobraram numa tupla. Duas estrelas juntam os argumentos nomeados que sobraram num dicionário.

Os nomes args e kwargs são só convenção. Quem faz o trabalho são as estrelas.

Uma estrela junta argumentos posicionais

Dentro da função, args é uma tupla comum. Ela fica vazia quando nada extra foi passado.

# *args collects positional arguments
def total(*args):
    return sum(args), type(args).__name__

print(total(1, 2, 3))
print(total())

Ele imprime:

(6, 'tuple')
(0, 'tuple')

Duas estrelas juntam argumentos nomeados

kwargs é um dicionário comum, na ordem em que os argumentos foram passados.

# **kwargs collects keyword arguments
def describe(**kwargs):
    return kwargs, type(kwargs).__name__

print(describe(name='ada', age=36))

Ele imprime:

({'name': 'ada', 'age': 36}, 'dict')

A ordem é fixa

Parâmetros normais, depois *args, depois parâmetros somente-nomeados, depois **kwargs. O Python obriga essa ordem.

# the order is fixed
def mixed(first, *args, key=None, **kwargs):
    return first, args, key, kwargs

print(mixed(1, 2, 3, key='k', extra=True))

Ele imprime:

(1, (2, 3), 'k', {'extra': True})

Qualquer coisa depois de *args só pode ser passada pelo nome.

Uma estrela sozinha força argumentos somente-nomeados

Se você não quer os argumentos posicionais extras, mas quer que quem chama nomeie as coisas, use uma estrela sozinha.

# a bare star forces keyword-only arguments
def connect(host, *, port=5432, timeout=30):
    return host, port, timeout

print(connect('localhost', port=5433))
try:
    connect('localhost', 5433)
except TypeError as err:
    print(type(err).__name__ + ':', err)

Ele imprime:

('localhost', 5433, 30)
TypeError: connect() takes 1 positional argument but 2 were given

Vale a pena fazer isso em qualquer função com várias opções, porque connect(host, 5433, 60) não diz nada a quem lê.

As mesmas estrelas desempacotam na chamada

Numa chamada, as estrelas significam o oposto. Elas espalham uma sequência ou um dicionário em argumentos.

# unpacking at the call site
def point(x, y, z):
    return x + y + z

coords = [1, 2, 3]
named = {'x': 1, 'y': 2, 'z': 3}
print(point(*coords), point(**named))

Ele imprime:

6 6

Repassando tudo adiante

Esse é o padrão por trás de todo wrapper e de todo decorator. Aceite qualquer coisa, repasse sem alterar.

# forwarding everything to another function
def logged(fn, *args, **kwargs):
    print('calling', fn.__name__, 'with', args, kwargs)
    return fn(*args, **kwargs)

print(logged(point, 1, 2, z=3))

Ele imprime:

calling point with (1, 2) {'z': 3}
6

O wrapper não precisa conhecer a assinatura da função que ele embrulha.

O que lembrar

  • *args é uma tupla de argumentos posicionais extras, **kwargs é um dicionário de argumentos nomeados extras.

  • A ordem é: normais, *args, somente-nomeados, **kwargs.

  • Um * sozinho torna somente-nomeados todos os parâmetros depois dele.

  • Numa chamada, as estrelas desempacotam em vez de juntar.

Se você se pegar escrevendo *args, **kwargs numa função que não é um wrapper, geralmente vale mais nomear os parâmetros. A assinatura é documentação.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.