定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了。对于函数的调用者来说,只需要知道如何传递正确的参数,以及调用函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。
位置参数
先写一个计算x平方的函数:
def p(x):
return x * x
对于p(x)函数,参数x就是一个位置参数。
当我们调用p函数时,必须传入有且仅有的一个参数x:
>>> p(5)
25
>>> p(15)
225
如果要计算x的立方怎么办,在定义一个p3函数,计算x的四次方,甚至更多次方呢?
把p(x),修改为p(x, n),用来计算x的n次方:
def p(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
rerurn s
对于这个修改后的p(x, n)函数,可以计算任意n次方:
>>> p(5, 2)
25
这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
由于经常计算x的平方,所以,完全可以把第二个参数n 的默认值设定为2:
def p(x, n = 2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
这样调用p(5),相当于调用p(5,2):
>>> p(5)
25
>>> p(5, 2)
25
对于n > 2的情况,必须明确传入n,比如p(5, 3)。
从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数需要注意几个问题:
- 必选参数在前,默认参数在后。
- 如何设置默认参数?
函数有多个参数是,把变化大的参数放在前面,变化小的参数放在后面,变化小的参数就可以作为默认参数。
默认参数最大的好处就是降低调用函数的难度。
比如,写一个一年级小学生注册信息的函数,需要传入name和gender两个参数:
def enroll(name, gender):
print('name:', name)
print('gender:', gender)
这样,调用enroll()函数只需要传入两个参数:
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
如果要继续传入年龄,城市等信息怎么办?这样会使调用函数的复杂度大大增加。
我们可以办年龄和城市设置为默认参数:
def enroll(name, gender, age = 6, city = 'Beijing'):
print('name:', name)
print('gender:', gender)
print('age:', age)
print('city:', city)
这样大多数学生注册不需要提供年龄和城市,只提供必需的两个参数:
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 6
city: Beijing
只有与默认参数不符的学生才需要提供额外的信息:
enroll('Bob', 'M', 7)
enroll('Adam', 'M', city = 'Tianjin')
默认参数有一个最大的坑:
先定义一个函数,传入一个list,再添加一个END再返回:
def add_end(L = []):
L.append('END')
return L
正常调用结果似乎不错:
>>> add_end([1,2,3])
[1,2,3,'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
当使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
默认参数是[],但是函数似乎每次都记住了上次添加了'END'后的list。
原因如下:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
要修改上面的例子,我们可以用None这个不变对象来实现:
def add_end(L = None):
if L is None:
L = []
L.append('END')
return L
可变参数
可变参数就是传入的参数个数是可变的,可以是1个,2个到任意个,还可以是0个。
以数学题为例,给定一组数字a,b,c... , 计算a平方+ b平方 + c平方 + ...... 。
要定义出这个函数,必须确定输入的参数,由于参数个数不确定,我们首先想到的把a,b,c....作为一个list或tuple传进来,这样,函数可以定义如下:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
调用时,先组装一个list或tuple:
>>> calc([1,2,3])
14
>>> calc([1,3,5,7])
84
所以把函数参数改为可变参数:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum
定义可变参数和定义一个list或tuple参数相比,在参数前加* 号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。
>>> calc(1,2)
5
>>> calc()
0
如果已经有一个list或tuple,要调用一个可变参数怎么办?
>>> nums = [1,2,3]
>>> calc(nums[0], nums[1], nums[2])
14
这种写法虽然可行,但是繁琐。所以Python允许在list或tuple前加* 号,把list或tuple的元素变成可变参数:
>>> nums = [1,2,3]
>>> calc(*nums)
14
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字在函数内部自动组装为一个dict。
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函数person除了必选参数name和age外,还接受关键字参数kw,在调用该函数时,可以只传入必选参数:
>>> person('Michael', 30)
name: Michael age: 30 other: {}
也可以传入任意个数的关键字参数:
>>> person('Bob', 35, city = 'Beijing')
name: Bob age: 35 other: {'city' : 'Beijing'}
>>> person('Adam', 45, gender = 'M' ,job = 'Engineer')
name: Adam age: 45 other: {'gender' : 'M', 'job' : 'Engineer'}
和可变参数类似,也可以先组装一个dict,然后,把该dict转换为关键字参数传进去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city = extra['city'], job = extra['job'])
name: jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
上面复杂的调用方法可以简化为:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw获得一个dict,注意kw获得dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
命名关键字参数
要限制关键字参数的名字,就可以用命名关键字参数,例如,只接受city和job作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数kw不同,命名关键字参数需要一个特殊分隔符,后面的参数被视为命名关键字参数。
>>> person('Jack', 24, city = 'Beijing', job = 'Engineer')
Jack 24 Beijing Engineer
如果函数定义中已经有一个可变参数,后面跟着的命名关键字参数就不需要一个特殊分隔符*了:
def person(name , age, *args, city, job):
print(name, age, args, city, job)
命名关键字参数必须传入参数名。
命名关键字参数可以有缺省值:
def person(name, age, *, city = 'Beijing', job):
print(name, age, city, job)
>>> person('Jack', 24, job = 'Engineer')
Jack 24 Beijing Engineer
参数组合
在Python中定义函数,可以选用必选参数,默认参数,可变参数,关键字参数和命名关键字参数,5中参数可以组合使用。
但是,参数定义的顺序必须是:必选参数,默认参数,可变参数,命名关键字参数,关键字参数。
def f1(a, b, c = 0, *args, **kw):
print('a = ', a, 'b = ', b, 'c = ', c, 'args = ', args, 'kw = ', kw)
def f2(a, b, c = 0, *, d, ** kw):
print('a = ', a, 'b = ', b, 'c = ', c, 'd = ', d, 'kw = ', kw)
在调用函数的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
>>> f1(1,2)
a = 1 b =2 c = 0 args = () kw = {}
>>> f1(1,2,c = 3)
a = 1 b = 2 c = 3 args =() kw = {}
>>> f1(1,2,3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1,2,3,'a','b',x =99)
a= 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>>f2(1,2, d =99,ext = None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext' : None}
最神奇的是通过一个tuple和dict,也可以调用上述函数:
>>> args = (1,2,3,4)
>>> kw = {'d': 99, 'x' : '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1,2,3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw ={'x': '#'}
对于任意函数,都可以通过类似func(*args , **kw)的形式调用,无论它的参数如何定义的。