python3 中使用 **kwargs 简化定义和调用

越来越发现 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 大大增加了我们编码的灵活度,简化了很多编程工作,还有更多的用法就等你自己来发掘啦!