O que as estrelas realmente fazem, a ordem obrigatória dos parâmetros, argumentos somente-nomeados e o desempacotamento na chamada.
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.