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.