Python *args和**kwargs
人生苦短,我用Python。

北静王 --中科院-虚拟经济与数据科学研究中心在读博士生,主要研究方向为NLP,DL,ML。
前言:
今天在研究代码的时候,review前几天的decorator函数并增添新的功能的时候,想做几个非常简单的decorator来实现对被装饰函数的参数检查和数值转换。由于参数检查的代码已经在之前完成了。所以,想利用叠加decorator的方式来实现动态修改函数的功能,这样能够最大降低程序的耦合度。但是现在是遇见的问题是,我的decorator在参数不确定的情况下,我们使用了*args
和**kwargs
两个参数组来进行泛化支持。但是,我们在不知道参数的情况下怎么能够完成参数的转换并传递呢。
1.泛化参数组
在之前的讲解decorator的前半部分中link,我讲解了怎么使用args
和kwargs
这两个参数进行参数不确定的参数传递。为了直观的显示这两个参数的实际意义,我们通过下面的代码来看清这两个犹抱琵琶半遮面的真实面目。
def parseArgs(*args, **kwargs):
print(type(args))
print(args)
print(type(kwargs))
print(kwargs)
parseArgs("test",hello="hello")
[out]:tuple
("test",)
dict
{"hello":"hello"}
通过上面的代码我们可以看出args负责的是位置参数,并放到了一个tuple中。而kwargs负责的是关键字参数,放到了一个dict中,通过key-value的方式存储。
再回到之前我们参数个数不确定的时候,我们使用decorator的编写方式如下:
from functools import wraps
def deco(func):
@wraps(func)
def inner(*args,**kwargs):
return func(*args,*kwargs)
return inner
@deco
def myFunc(arg1,arg2,arg3)
参数arg1
,arg2
,arg3
首先会通过decorator的inner
函数传递到函数内部,而返回的 func
函数则直接使用的是传递进来的参数,这样也就保持了参数的统一不变特性。回到我最初的需求上面,我想通过一个decorator来实现参数的类型统一或是类型的转换(比如数组变为numpy array,或是str变为int等)。我们能够动手脚的地方则是在inner
内部,在func()
被调用之前。此时需要解决的一个问题则是将传递到func
的参数还是保持像inner
接受的参数类型一样(也就是args是个tuple
类型,而kwargs是个dict
类型的变量)。
def deco(func):
@wraps(func)
def inner(*args,**kwargs):
# do something
args1=list()
args2=dict()
for _args in args:
# transfer the data
args1.append(str(_args)
for k,v in kwargs.items():
# transfer the data
args2[l]=str(v)
args=tuple(args1)
kwargs=args2
return func(*args,**kwargs)
return inner
上面的改变只是最基本的类型转换,在func
函数被调用之前,实际上你可以定制的功能十分多。同时你可以在这里做些数据的预处理,归一化,标准化或是其他的数据处理工作。不过为了程序功能的解耦,还是do one thing in a time
2. *
在参数传递中的作用
*更像一个拆包工具,他的主要作用就是将list
,tuple
,set
,dict
等类型的参数拆成单个的数值并传递到函数中当位置参数使用。
def f(a,b,c):
print a,b,c
f(1,2,3)
a=(1,2,3)
b=[1,2,3]
c={1,2,3}
d={1:"1",2:"2",3:"3"}
e={"1":"1","2":"2","3":"3"}
f(a)
f(b)
f(c)
f(d)
f(e)
[out]:1,2,3
1,2,3
1,2,3
1,2,3
1,3,2 #这里为什么是这个顺序,答案将在文章末尾揭晓
当然,这个拆包工具可以只拆位置参数的部分包,也就是可以将拆包工具和位置参数混用
a=[1,2]
f(3,*a)
[out]: 3 1 2
2. *
在参数定义时候的作用
上一小节中我们研究的是*
在参数传递的时候的作用是作为拆包工具,那么这一小节中将要研究的则是*
在参数定义的时候作用,他的作用则是相当于一个打包工具。在我们不知道具体的参数有多少个的时候,使用*
前缀定义的关键词可以将所有“预想不到”的参数进行打包,也就是先把他看做是一个参数集合,等到实际调用的时候才填充这个集合。这在动态语言中很好实现。
def f(a,*other):
print type(other)
print other
f(1,2,3)
[out]: <type 'tuple'>
(2,3)
所以,上面的两个小节介绍的方法可以看做是相互对应的,在函数定义的时候,*
的作用就是打包,将不确定个数的参数打包到tuple里面。在参数传递的时候,可以将tuple或是list的参数直接解包当做位置参数来使用。
3. **
的作用
前面提到的打包和解包参数的*
在使用的时候,作用的是位置参数。位置参数在调用的时候除了使用直接传递参数之外,还可以使用关键字传参。而**
则是作用在关键字传参数上面的,在参数定义和参数传递过程中也是起着打包和拆包的作用,具体的使用方式和*
在参数定义和参数传递过程中的使用方式相仿。不过拆包和解包的对象从tuple
换成了dict
#函数定义
def f(a, b="b",c="c"):
print(a,b,c)
f("a","b")
a={"b":"newB","c":"newC"}
b={"c":"newC"}
f("a",**a)
f("b",**b)
[out]:('a','b','c')
('a','newB','newC')
('a','newB','newC')
('a','newB','newC')
从上面的例子中,我们可以看出**是对一个dict的变量进行解包,在当做参数传递的时候能够将dict按照关键字参数的使用方式进行传递。
当然,除了用在关键字传递上面,例外一种使用方式则是用在定义参数的时候。这个时候**
的作用则是将参数进行打包。在之后使用的时候,这个参数变量则可以看做是一个dict类型的变量。
def f(**pars):
for key,value in pars.items():
print("key=%s; value=%s"%(key,value))
b={"b":"b","c":"c"}
f(**b)
[out]:key=c; value=c
key=b; value=b
总结
上面提到的*
和**
主要用在参数定义和参数传递的时候。对于参数传递来说,*
和**
都是用来解包的,对于参数定义的时候,*
和**
都是用来打包的。两者应用在对于参数个数不确定的情况下。*
主要是用来对tuple
型参数打包或解包,对应的是函数的位置参数。而**kwargs
则是主要用来对dict
型参数,对应的是函数的关键字参数。此时,如果你再回到文章开始的时候我提出的问题并稍作分析则就能够提出问题的解决之道了。
回答文章开始的时候,传递了不同类型参数的时候,输出不是预期的原因:因为*
对应的是tuple
问题,所以在这里,会将一切的传递进来的参数都先转换成tuple
类型。你可以先将传递进来的非tuple
类型的参数进行转换看下是不是这样,结果肯定是的。如果我们想传递的dict
的key的顺序和定义或是添加的时候一致,那么我们该怎么办呢?我能想到的一个办法就是使用collections中的OrderedDict。当然还是不赞成你用*
传递非tuple
类型的参数的。