Python 里的单星号和双星号到底做了什么,参数必须按什么顺序排列,以及怎样把参数原样转交给另一个函数。
参数前面加一个星号,会把多出来的位置参数收集成元组。加两个星号,会把多出来的关键字参数收集成字典。
args 和 kwargs 这两个名字只是约定俗成,真正起作用的是星号。
单星号收集位置参数
在函数内部,args 就是普通的元组。没有传多余的参数时,它是空的。
# *args collects positional arguments
def total(*args):
return sum(args), type(args).__name__
print(total(1, 2, 3))
print(total())
输出:
(6, 'tuple')
(0, 'tuple')
双星号收集关键字参数
kwargs 就是普通的字典,顺序和传参时一致。
# **kwargs collects keyword arguments
def describe(**kwargs):
return kwargs, type(kwargs).__name__
print(describe(name='ada', age=36))
输出:
({'name': 'ada', 'age': 36}, 'dict')
顺序是固定的
先是普通参数,然后是 *args,接着是仅限关键字参数,最后是 **kwargs。Python 强制要求这个顺序。
# 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))
输出:
(1, (2, 3), 'k', {'extra': True})
*args 后面的参数只能按名字传。
单独一个星号强制使用关键字参数
如果你不需要额外的位置参数,但希望调用方写明参数名,就单独放一个星号。
# 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)
输出:
('localhost', 5433, 30)
TypeError: connect() takes 1 positional argument but 2 were given
只要函数有好几个选项,就值得这么做,因为 connect(host, 5433, 60) 让读代码的人一头雾水。
调用时,星号用来解包
在调用时,星号的意思正好相反:把序列或字典展开成参数。
# 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))
输出:
6 6
把参数全部转交出去
所有包装函数和装饰器背后都是这个模式:什么参数都接收,原封不动地转交。
# 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))
输出:
calling point with (1, 2) {'z': 3}
6
包装函数不需要知道被包装函数的签名。
要点
*args是多余位置参数组成的元组,**kwargs是多余关键字参数组成的字典。- 顺序是:普通参数、
*args、仅限关键字参数、**kwargs。 - 单独一个
*会让它后面的参数只能按关键字传。 - 在调用处,星号负责解包,而不是收集。
如果你发现自己在一个不是包装函数的函数上写了 *args, **kwargs,通常最好把参数一个个写出来。函数签名本身就是文档。