python参数传递*args和**kwargs
和*
实际上真正的Python参数传递语法是 *
和 **
。*args
和 **kwargs
只是一种约定俗成的编程实践。我们也可以写成 *vars
和 **kvars
。就如同其他常规变量的命名一样, args
和 kwargs
只是一种习惯的名称。
*args
和 **kwargs
一般是用在函数定义的时候。二者的意义是允许定义的函数接受任意数量的参数。也就是说我们在函数被调用前并不知道也不限制将来函数可以接收的参数数量。在这种情况下我们可以使用 *args
和 **kwargs
。
接下来我们分别分析 *args
和 **kwargs
在函数定义时(即在形参列表中)和在函数调用时的用法。
在函数定义时的*和**(即函数形参列表中的*和**)
在函数定义时的*args
*args
用来表示函数接收可变长度的非关键字参数列表作为函数的输入。我们通过一个例子来体会参数列表中的 *args
的用法:
def test_args(normal_arg, *args):print("normal arg:" + normal_arg)print(type(args))for arg in args:print("another arg through *args :" + arg)test_args("normal", "Python", "C", "C++")
输出:
normal arg:normal
<class 'tuple'>
another arg through *args :Python
another arg through *args :C
another arg through *args :C++
在函数定义时的**kwargs
**kwargs
表示函数接收可变长度的关键字参数字典作为函数的输入。当我们需要函数接收带关键字的参数作为输入的时候,应当使用 **kwargs
。我们可以通过以下这个例子来进一步理解 **kwargs
。
def test_kwargs(**kwargs):if kwargs is not None:print(type(kwargs))for key, value in kwargs.items():print("{} = {}".format(key,value))test_kwargs(name="python", value="5")
输出:
<class 'dict'>
name = python
value = 5
如何处理关键字参数中的未知性
虽说参数列表中的 *和**使得我们可以接收任意多个参数变量,但是我们在设计函数时肯定也不是随便什么参数都去处理的,函数的调用者应该明白在 *args
和 **kwargs
中需要传递什么参数,可以传递什么参数(比如深度学习模型中的模型结构的设置,如模型深度,dropout概率等)。并且我们在函数体中来处理这些参数。
然而,我们并不知道调用者在调用时会传什么,哪些又应该在调用时使用默认值。这里笔者参考了 timm
视觉库对 ViT 的实现。
在_create_vision_trainsformer 函数中,我们只要处理和我们相关的传入参数:
def _create_vision_transformer(variant, pretrained=False, default_cfg=None, **kwargs):default_cfg = default_cfg or default_cfgs[variant]if kwargs.get('features_only', None):raise RuntimeError('features_only not implemented for Vision Transformer models.')# NOTE this extra code to support handling of repr size for in21k pretrained modelsdefault_num_classes = default_cfg['num_classes']num_classes = kwargs.get('num_classes', default_num_classes)repr_size = kwargs.pop('representation_size', None)if repr_size is not None and num_classes != default_num_classes:# Remove representation layer if fine-tuning. This may not always be the desired action,# but I feel better than doing nothing by default for fine-tuning. Perhaps a better interface?_logger.warning("Removing representation layer for fine-tuning.")repr_size = Nonemodel = build_model_with_cfg(VisionTransformer, variant, pretrained,default_cfg=default_cfg,representation_size=repr_size,pretrained_filter_fn=checkpoint_filter_fn,pretrained_custom_load='npz' in default_cfg['url'],**kwargs)return model
可以看到,我们传入的 kwargs
是一个字典,这里使用了字典的 get
、pop
两种方法来处理。
python字典的get和pop方法
pop
dict.pop(key[,default])
删除字典给定键 key 及对应的值,返回值为被删除的值,若 key 不存在,则返回 default 参数所指定的默认值。
get
dict.get(key, default=None)
函数返回指定键的值,若 key 不存在,则返回 default 参数所指定的默认值。
通过这两种方法,我们就能够实现在 kwargs 中没有传入参数时使用默认值,而在有指定参数时,使用传入值。
在函数调用时的*和**
-
列表前面加星号作用是将列表解开成两个独立的参数,传入函数,
-
字典前面加两个星号,是将字典解开成独立的元素作为形参。
如果我们有一个加法函数 add()
:
def add(a, b):return a + b
函数调用时的*
我们可以这样调用它:先构造一个列表,在用 * 将列表解开,将其中的值作为参数逐个传入函数的参数列表:
data_list = [1, 2]
print(add(*data_list))
# 输出:3
就相当于
print(add(1, 2))
我们甚至可以直接打印出 * 解开列表之后的参数序列:
print(*data_list)
# 输出:1 2
函数调用时的**
我们还可用字典的形式来调用它:构造一个字典,键名为要调用函数的形参名称,值为我们要传递的参数:
data_dict = {'a': 1, 'b': 2}
print(add(**data_dict))
# 输出:3
相当于:
print(add(a=1, b=2))
注意这里我们构造的字典的键名必须对应函数形参列表中的形参名称,否则汇报错,比如
data_dict = {'a': 1, 'c': 2}
print(add(**data_dict))
相当于:
print(data(a=1, c=2))
将会报错:
TypeError: add() got an unexpected keyword argument 'c'
两种使用方式的统一理解
以关键字参数 **kwargs
为例,我们这样考虑:
我们提到过,对于一个字典 dict={'a': 1, 'b': 2}
,给它加上 **
号 ,相当于是将其中的元素解开单拎出来,即 **dict
相当于 a=1, b=2
,这正好符合我们调用函数传递参数时的写法。
而当 **kwargs
在中形参列表中时,我们传入 a=1, b=2
,看一下,正好对应我们上面的 **dict
(只是名字不一样,开始提到过,这无妨),那也把 **
号去掉后,kwargs
就相当于我们上面的 dict
,是一个字典:{'a': 1, 'b': 2}
,这就说明了为什么我们上面介绍函数定义时的 **kwargs
的例子中,type(kwargs)
为 dict
,并且在函数体中我们也是通过调用字典类的方法(如 items
、pop
、get
)来处理它。
Ref:
https://www.jianshu.com/p/be92113116c8
https://github.com/rwightman/pytorch-image-models