Python 函数参数*args和**kwargs

尽管*args**kwargs参数不受重视,但这它们是Python中非常有用的特性。了解其中的潜能会让你成为更高效的开发者。

*args**kwargs参数到底有什么用呢?它们能让函数接受可选参数,因此能在模块和类中创建灵活的API:

def foo(required, *args, **kwargs):
    print(required)
    if args:
        print(args)
    if kwargs:
        print(kwargs)

上述函数至少需要一个名为required的参数,但也可以接受额外的位置参数和关键字参数。

如果用额外的参数调用该函数,args将收集额外的位置参数组成元组,因为这个参数名称带有*前缀。

同样,kwargs会收集额外的关键字参数来组成字典,因为参数名称带有**前缀。

如果不传递额外的参数,那么argskwargs都为空。
在用各种参数组合来调用这个函数时,Python会将位置参数或关键字参数分别收集到argskwargs参数中:

>>> 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}

这里需要说清楚的是,参数argskwargs只是一个命名约定。哪怕将其命名为*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收集额外的关键字参数组成字典。

  • 实际起作用的语法是***argskwargs只是约定俗成的名称(但应该坚持使用这两个名称)。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程