越来越发现 python 真是一门灵活的语言。

可变参数列表 *args**kwargs 是我们经常在一些函数定义中见到的,那么我们实际如何利用它们呢?我们自己的函数中怎么使用呢?

事实上,如果函数参数中含有它们,只需要分别将它们视作列表和字典即可,用法也类似。

*args

来看一个例子:

1
2
3
def foo(*args):
    for arg in args:
        print(f"arg={arg})

实际中,如果我们想要接收不知数量的多个参数,但对参数的名称没有要求的话(强调这一点是为了与 **kwargs 做区别),比如说我们想写一个求任意个参数中最大值的函数,就可以这样写:

1
2
3
4
from functools import reduce

def max(*values):
	return reduce(lambda x,y: x if x > y else y, values)

这里需要注意,*args 只是一个约定俗成的参数名称,实际中只要用一个星号 * 接参数,这个参数就是一个可变参数。

在上面的函数中,我们使用了 reduce,从前到后逐个比较,取得其中的最大值,可见 values 在这里和一个普通的列表没有区别。

执行示例:

1
2
3
4
>>> max(1)
1
>>> max(-1, 2, 0, -3, 4, 2)
4

再看下面的函数:

1
2
def prtArgs(*args):
    print(args)

执行示例:

1
2
>>> print(3, '个', False)
(3, '傻', False)

*args 就说这么多,有趣的是 **kwargs 的用法。

**kwargs

从名字就可以看出一二,它是一个‘keyword args’,也就是带有关字的,首先看它的用法:

1
2
3
def foo(**kwargs):
    for k, v in kwargs.items():
	    print(f"{k}: {v}")

执行示例:

1
2
3
4
5
6
7
8
>>> foo(a=1, b='2', c=False)
a: 1
b: 2
c: False
>>> foo(1,3,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes 0 positional arguments but 3 were given

可见,如果要填充 **kwargs 的参数,必须使用 k=v 的形式,也就是要指配一个参数名,而 kwargs.items() 就会返回一个字典,其键为参数名,值为参数值,再看一个例子:

1
2
3
4
def foo(a, **kwargs):
    print(f"a={a}")
    for k, v in kwargs.items():
        print(f"{k}:{v}")

加了一个固定参数 a ,如果我们为 **kwargs 再指定一个 a 会怎样呢?

1
2
3
4
>>> foo(3, a='2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'a'

可见,**kwargs 中的参数不能与其他参数同名,而这也说明它里面的参数具有同等效力(废话)。

话不多说,我们考虑这样一种情况:我们定义了一个拥有很多变量的类,大概长这样:

1
2
3
4
5
6
7
class Jumbled:
    __a = 1
    __b = 2
    __c = 3
    __d = 4

j = Jumbled()

而我们在修改 j 的时候,有时候要修改 a,有时候要修改 d,每次一个,不会同时修改,而它们又是私有的,不能外部修改,所以我们会考虑用一个这样的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    def alter(self, a=None, b=None, c=None, d=None):
        if j:
            if a is not None:
                self.__a = a
            if b is not None:
                self.__b = b
            if c is not None:
                self.__c = c
            if d is not None:
                self.__d = d

    def __str__(self):
        return f"a={self.__a} b={self.__b} c={self.__c} d={self.__d} "

但是只有四个参数就搞得这么复杂,10 个参数的话就会有很多无用代码,怎么简化呢?我们可以考虑使用 **kwargs,就可以这样写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Jumbled:
    __a = 1
    __b = 2
    __c = 3
    __d = 4

    def alter(self, **kwargs):
        for k, v in kwargs.items():
            exec('self._Jumbled__' + k + ' = v')

    def __str__(self):
        return f"a={self.__a} b={self.__b} c={self.__c} d={self.__d} "

执行一下:

1
2
3
4
>>> j = Jumbled()
>>> j.alter(b=0)
>>> print(j)
a=1 b=0 c=3 d=4 

利用 **kwargs,只将要修改的变量填进去就可以进行修改,我们也可以直接使用 __setattr__ 方法,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Jumbled:
    a = 1
    b = 2
    c = 3
    d = 4

    def alter(self, **kwargs):
        for k, v in kwargs.items():
            self.__setattr__(k, v)

    def __str__(self):
        return f"a={self.a} b={self.b} c={self.c} d={self.d} "

执行一下:

1
2
3
4
>>> j = Jumbled()
>>> j.alter(a=5, d=11)
>>> print(j)
a=5 b=2 c=3 d=11 

可见,*args**kwargs 大大增加了我们编码的灵活度,简化了很多编程工作,还有更多的用法就等你自己来发掘啦!