尽管*args
和**kwargs
参数不受重视,但这它们是Python中非常有用的特性。了解其中的潜能会让你成为更高效的开发者。
*args
和**kwargs
参数到底有什么用呢?它们能让函数接受可选参数,因此能在模块和类中创建灵活的API:
def foo(required, *args, **kwargs):
print(required)
if args:
print(args)
if kwargs:
print(kwargs)
上述函数至少需要一个名为required
的参数,但也可以接受额外的位置参数和关键字参数。
如果用额外的参数调用该函数,args
将收集额外的位置参数组成元组,因为这个参数名称带有*
前缀。
同样,kwargs
会收集额外的关键字参数来组成字典,因为参数名称带有**
前缀。
如果不传递额外的参数,那么args
和kwargs
都为空。
在用各种参数组合来调用这个函数时,Python会将位置参数或关键字参数分别收集到args
和kwargs
参数中:
>>> foo()
TypeError:
"foo() missing 1 required positional arg: 'required'"
>>> foo('hello')
hello
>>> foo('hello', 1, 2, 3)
hello
(1, 2, 3)
>>> foo('hello', 1, 2, 3, key1='value', key2=999)
hello
(1, 2, 3)
{'key1': 'value', 'key2': 999}
这里需要说清楚的是,参数args
和kwargs
只是一个命名约定。哪怕将其命名为*parms
和**argv
,前面的例子也能正常工作。实际起作用的语法分别是星号(*
)和双星号(**
)。
不过还是建议你坚持使用公认的命名规则以避免混淆。(这样还有机会每隔一段时间就大喊出“argh”和“kwargh”。)
Python 函数参数*args和**kwargs 传递可选参数或关键字参数
可选参数或关键字参数还可以从一个函数传递到另一个函数。这需要用解包操作符*
和**
将参数传递给被调用的函数。
参数在传递之前还可以修改,来看下面这个例子:
def foo(x, *args, **kwargs):
kwargs['name'] = 'Alice'
new_args = args + ('extra', )
bar(x, *new_args, **kwargs)
这种技术适用于创建子类和编写包装函数。例如在扩展父类的行为时,子类中的构造函数不用再带有完整的参数列表,因而适用于处理那些不受我们控制的API:
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
class AlwaysBlueCar(Car):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.color = 'blue'
>>> AlwaysBlueCar('green', 48392).color
'blue'
AlwaysBlueCar
构造函数只是将所有参数传递给它的父类,然后重写一个内部属性。这意味着如果父类的构造函数发生变化,AlwaysBlueCar
仍然可以按预期运行。
不过缺点是,AlwaysBlueCar
构造函数现在有一个相当无用的签名——若不查看父类,无从知晓函数会接受哪些参数。
一般情况下,自己定义的类层次中并不会用到这种技术。这通常用于修改或覆盖某些外部类中的行为,而自己又无法控制这些外部类。
但这仍然属于比较危险的领域,所以最好小心一点(不然可能很快就会因为另一个原因而尖叫出声)。
该技术可能有用的另一个场景是编写包装函数,如装饰器。这种情况下,我们通常也想接受所有传递给包装函数的参数。
如果能在不复制和粘贴原函数签名的情况下就做到这一点,就会让代码更易于维护:
def trace(f):
@functools.wraps(f)
def decorated_function(*args, **kwargs):
print(f, args, kwargs)
result = f(*args, **kwargs)
print(result)
return decorated_function
@trace
def greet(greeting, name):
return '{}, {}!'.format(greeting, name)
>>> greet('Hello', 'Bob')
<function greet at 0x1031c9158> ('Hello', 'Bob') {}
'Hello, Bob!'
这样的技术使我们有时很难在“代码足够明确”和“不要重复自己”(DRY)原则之间保持平衡。这是个艰难的选择,有条件的话建议咨询一下同事的意见。
Python 函数参数*args和**kwargs 关键要点
-
*args
和**kwargs
用于在Python中编写变长参数的函数。 -
*args
收集额外的位置参数组成元组。**kwargs
收集额外的关键字参数组成字典。 -
实际起作用的语法是
*
和**
。args
和kwargs
只是约定俗成的名称(但应该坚持使用这两个名称)。