函数原型
我们用之前学过的 max 函数 来举例子。它在官方文档中介绍函数的说明是这样子:
max(iterable,*[, key, default])
里面包含了一大堆内容,诸如 iterable、*、[ ] 这些。
函数原型的文档都按照这样的方式编写,因此在学习函数前我们有必要学习一下如何理解函数原型的相关知识文章。
首先我们要了解 形参 和 实参 到底是什么。
实参 (argument)
全称为”实际参数”是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
形参 (parameter)
全称为”形式参数” 由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应,并且实参必须要有确定的值。
用代码举一个例子,那就是:
实参是在调用函数的时候给出的 / 真实的数据信息。
welcome('tom')
welcome('lily')
这里的 tom 和 lily 就是实参。
形参是在定义函数的时候给出的 / 定义函数的时候的变量。
def welcome(a):
print('welcome', a)
这里的 a 就是形参。a 是一个变量,实际值为 welcome。
我们已经知道,可以用于 for 循环的数据类型有这些:
一类是集合数据类型,如:list、tuple、dict、set、str 等;
另一类是 generator,包括生成器和带 yield 的 generator function。(这个之后会讲)
这些可以直接作用于 for 循环的对象统称为可迭代对象:Iterable。
接下来我们来讨论 * 。
首先我们需要知道 Python 的参数类型:
- 位置参数(position arguments,这是官方定义,也就是其他语言所说的参数)
- 默认参数(类似 C++ 的默认参数)
- 可变参数
- 命名关键字参数
- 关键字参数(keyword argument)
位置参数
位置参数(position argument)就是其他语言的参数,其他语言没有分参数的种类是因为只有这一种参数,所有参数都遵循按位置一一对应的原则。
首先我们来写一个计算 $x^2$ 的函数:
def power(x):
return x * x
在这里,对于 power(x) 函数,参数 x 就是一个位置参数。
当我们调用 power 函数时,必须传入有且仅有的一个参数 x:
>>> power(5)
25
>>> power(15)
225
现在问题来了,如果我们要计算 $x^3$ 怎么办?那我们再定义一个 power3 函数呗。那还要接着算 $x^4$、$x^5$……呢?我们不可能定义无限多个函数。
那就换个思路,我们把定义的 power(x) 换成 power(x, n),这样我们就可以计算 $x^n$ 了。
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
对于这个修改后的 power(x, n) 函数,可以计算 n 次方:
>>> power(5, 2)
25
>>> power(5, 3)
125
修改后的 power(x, n) 函数里面有两个参数:x 和 n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数 x 和 n。
默认参数
之前的 power(x, n) 函数定义没什么问题,但是旧的调用代码已经失效了,因为我们增加了一个参数,因此导致旧的代码因为缺少一个参数而无法正常调用:
>>> power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'
我们使用了两个参数 x 和 n,但是输入的时候我们只输入了一个 x,这导致调用函数 power() 的时候缺少了一个位置参数 n。这时候 默认参数 就派上用场了。
因为我们经常计算的是 $x^2$,所以我们完全可以把第二个参数 n 的默认值设置为 2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
这样我们调用 power(5) 的时候,相当于调用了 power(5, 2)。
>>> power(5)
25
>>> power(5, 2)
25
而对于 n > 2 的其他情况,就必须明确地传入 n,比如 power(5, 3)。
关键字参数
关键字参数运行你传入 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'}
函数形参列表中,符号 * 表示后面的形参只能为 关键字参数(keyword argument),不能为 位置参数(positional argument),也就是说, max 函数要这样用:
In [5]: a = [1,2,3,4,2,2,3]
In [6]: max(a,key=lambda x: a.count(x), default=1)
Out[6]: 2
定义函数 f,参数 b 位于 * 后面,只能为关键字参数:
In [116]: def f(a,*,b):
...: pass
In [117]: f(a,b=1)
In [118]: f(a,1) # 这种调用是错误的
TypeError: f() takes 1 positional argument but 2 were given
再看一个内置函数 sum:
sum(iterable, /, start=0)
看到形参列表前面有一个 /,它表示 / 前的参数只能是位置参数,不能是关键字参数。
因此,以下调用是合法的:
In [18]: a = [1,3,2,1,4,2]
In [19]: sum(a,2) # start=2 表示求和的初始值为 2
Out[19]: 15
以下调用是非法的,因为 iterable 参数不能被赋值为关键字实参:
In [23]: sum(iterable=a,start=2)
TypeError: sum() takes no keyword arguments
平时大家看到的 max 函数其实是这样用的:
In [7]: max([1,2,3,4,2,2,3])
Out[7]: 4
在这里 [] 表示里面的形参是可选项。
max 函数可以被如下几种形式调用:
max(iterable)
max(iterable,*, key)
max(iterable,*,default)
max(iterable,*, key, default)
不能被这么调用:
max(*, key)
iterable 没有默认值,所以它是不能被省略的,必须要给出一个实参。
扩展 >>
可变参数
在 Python 函数中还可以定义可变函数。顾名思义,可变参数就是传入的参数个数是可变的,可以是 1 个、2 个到任意个,还可以是 0 个。
我们用一道数学题来举例子:
给定一组数字 $a$,$b$,$c$……请计算 $a^2+b^2+c^2+$…… 作为一个 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
如果利用可变参数,调用函数的方式可以简化成这样:
>>> 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,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括 0 个参数:
>>> 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
*nums 表示把 nums 这个 list 的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过 kw 检查。
仍以 person() 函数为例,我们希望检查是否有 city 和 job 参数:
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
但是调用者仍可以传入不受限制的关键字参数:
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收 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)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
由于调用时缺少参数名 city 和 job,Python 解释器把这 4 个参数均视为位置参数,但 person() 函数仅接受 2 个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
由于命名关键字参数 city 具有默认值,调用时,可不传入 city 参数:
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个 * 作为特殊分隔符。如果缺少 * ,Python解释器将无法识别位置参数和命名关键字参数:
def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass