在上一篇文章『[Python]给函数传递不定个数的参数』
中,我们主要讲解了*
在函数定义和函数调用阶段的不同作用,并留了一个小问题:
我们用
*
定义了add_int(*a)
函数,现在有a_list=[1,2,3]
,那么我们调用add_int(*a_list)
会产生什么效果呢?
如果文章你已看懂,那不难知道,最后被传到函数当中的a
是(1,2,3)
,因为经历了先用*
解压——把a_list
变成1, 2, 3
并传到函数中,即add_int(1, 2, 3)
,再用*
打包——把传进来的1, 2, 3
变成(1, 2, 3)
并赋给函数的形参a
同时我们在上一篇文章中留了一个不求甚解的点——python函数的两种传递参数的方式,这里我们详细展开,并将介绍一种新的、跟*
类似的符号的用法
按位置传递
和按关键字传递
假设已预先定义好一个执行两数相加的函数:
def add_2_int(a1, a2):
return a1+a2
现在要计算1+2
的结果,最自然的调用方式是add_2_int(1,2)
,这样1
在函数中被赋予a1
,2
被赋予a2
,这种赋值方式是按照1, 2
和 a1, a2
在位置上的一一对应关系来赋值的,所以被称作按位置传递
。其实我们还可以有另外的调用方式:add_2_int(a2=2, a1=1)
,显示地告诉函数,我要给a2
赋值为2
,a1
赋值为1
,这是按照参数关键字来给参数赋值,与顺序、位置无关,所以叫按关键字传递
基于此,再次回顾一下上篇文章中*
的用法,如果我们的调用方式是add_2_int(*[1,2] )
,这是哪种参数传递方式呢?——答案当然是,按位置传递
,因为[1,2]
被*
解压成 1, 2
传到函数中,与add_2_int(1,2)
等价。
至此,聪明的读者肯定要问了,那按关键字传递
有没有类似的『解压』用法呢?——且往下看
我们先定义一个简单的类——human,定义方式如下
class human():
def __init__(self, gender = 'female', height = 160, weight = 55):
print 'gender: ', gender
print 'height: ', height
print 'weight: ', weight
有性别(gender)、身高(height)、体重(weight)三种基本属性,这三种基本属性都有默认值。
接下来我们实例化一个human,取名叫 Catherine
Catherine = human(height=173,weight='67')
Catherine的性别沿用了human类的默认值,所以没有在参数中给出,而身高体重都根据Catherine的实际情况做了调整,所以实例化类之后的结果是
gender: female
height: 173
weight: 67
不难发现,我们这里对类进行初始化时,采用的方式是按关键字传递
,而关键字
和其取值
,很容易让人联想到字典里的key
和value
,所以——如果关键字及其取值是『字典』形式,那我们就可以用一些方法『解压』再传给函数了,而对于『字典』的解压方式,显然不能用上面提到的*
,因为*
只能解压一个序列,像列表、元组等等,这里用到的解压方式,是*
的孪生兄弟——**
,具体用法如下:
dict_Catherine = {'weight':67,'height':173}
Catherine = human(**dict_Catherine)
**
将自动解压出dict_Catherine
里的key: value
对儿,并将value
赋值给函数中与key
名字相同的形参(注意,这是在函数调用阶段使用**
)
然而,只有三种基本属性的human显然是弱爆了,这个世界还有强大的变种人的存在。为了迎合变种人的各类飘忽不定的属性,我们必须重新定义一个新的human_mutant类,以适应这种新人类的『不定』属性需求,方式如下:
class human_mutant():
def __init__(self, gender = 'male', height = 170, weight = 65, **X):
print 'gender: ', gender
print 'height: ', height
print 'weight: ', weight
for key,value in X.iteritems():
print key, ': ',value
不难发现,变种人除了人类的三种属性,另外特意为其量身定做了一个未知的X
元素,并用**
作用在X
元素上,其效果是什么呢?我们看下实例化的结果。
这里我们先实例化一个X教授,代码如下
ProfessorX = human_mutant(ability = 'mind-control', hairstyle = 'bald', height = 175)
我们给X教授新添了ability
、hairstyle
等新参数,并且修改了身高。这些新参数并未在函数定义时给出,显然,它被写到了X
中。运行效果为
gender: male
height: 175
weight: 65
hairstyle : bald
ability : mind-control
所以**
在函数定义阶段的作用就是:将未出现在函数参数中的那些按关键字传递
的参数及其值,收集起来『打包』成一个字典。比如我们在初始化X教授时,用关键字传递的方式传了ability
和hairstyle
,然而它们没有出现在函数定义的参数中,这时**
的作用就是收集它们,变成字典{'ability': 'mind-control', 'hairstyle': 'bald'}
,赋值给参数X
。
OK,讲得差不多了,我们再娱乐一下,实例化一个我很喜欢的万磁王
Magneto = human_mutant(ability = 'metal-control', guard = 'helmet', height = 180, lover = 'ProfessorX')
我们给他加了新护具——头盔,以及超能力——控制金属,还有他的好基友——X教授。
至此,在Python中给函数传递不定『个数』和不定『关键字』的方法我们都搞定了,稍作总结:
函数定义时,*
可以将按位置传递
进来的参数『打包』成元组(tuple)类型**
可以将按关键字传递
进来的参数『打包』成字典(dictionary)类型
函数调用时,*
可以『解压』待传递到函数中的 元组、列表、集合、字符串等类型(关于字符串的解压,我在文中没有提及,有兴趣的读者可以自行尝试),并按位置传递
到函数入口参数中**
可以『解压』待传递到函数中的字典,并按关键字传递
到函数入口参数中