Un asterisco delante de un parámetro junta los argumentos posicionales que sobran en una tupla. Dos asteriscos juntan los argumentos por palabra clave que sobran en un diccionario.
Los nombres args y kwargs son solo convención. El trabajo lo hacen los asteriscos.
Un asterisco junta los argumentos posicionales
Dentro de la función, args es una tupla normal. Está vacía cuando no se pasó nada de más.
# *args collects positional arguments
def total(*args):
return sum(args), type(args).__name__
print(total(1, 2, 3))
print(total())
Imprime:
(6, 'tuple')
(0, 'tuple')
Dos asteriscos juntan los argumentos por palabra clave
kwargs es un diccionario normal, en el orden en que se dieron los argumentos.
# **kwargs collects keyword arguments
def describe(**kwargs):
return kwargs, type(kwargs).__name__
print(describe(name='ada', age=36))
Imprime:
({'name': 'ada', 'age': 36}, 'dict')
El orden es fijo
Parámetros normales, después *args, después parámetros solo por palabra clave, después **kwargs. Python lo exige.
# 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))
Imprime:
(1, (2, 3), 'k', {'extra': True})
Todo lo que venga después de *args solo se puede pasar por nombre.
Un asterisco solo obliga a usar palabras clave
Si no quieres los argumentos posicionales de más pero sí quieres que quien llame nombre las cosas, usa un asterisco solo.
# 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)
Imprime:
('localhost', 5433, 30)
TypeError: connect() takes 1 positional argument but 2 were given
Vale la pena hacerlo en cualquier función con varias opciones, porque connect(host, 5433, 60) no le dice nada a quien lo lee.
Los mismos asteriscos desempacan en la llamada
En una llamada, los asteriscos significan lo contrario. Reparten una secuencia o un diccionario como 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))
Imprime:
6 6
Pasar todo hacia adelante
Este es el patrón detrás de cada wrapper y cada decorador. Acepta cualquier cosa y pásala sin tocarla.
# 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))
Imprime:
calling point with (1, 2) {'z': 3}
6
El wrapper no necesita conocer la firma de la función que envuelve.
Qué recordar
-
*argses una tupla de argumentos posicionales de más,**kwargses un diccionario de argumentos por palabra clave de más. -
El orden es: normales,
*args, solo por palabra clave,**kwargs. -
Un
*solo hace que los parámetros que vengan después sean solo por palabra clave. -
En una llamada, los asteriscos desempacan en lugar de juntar.
Si te encuentras escribiendo *args, **kwargs en una función que no es un wrapper, normalmente conviene nombrar los parámetros. La firma es documentación.