函数的意义就是保证和隐藏实现细节,对调用者来说,只要知道传什么参数和返回什么值。

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

位置参数

位置参数非常简单明白,就是一个最普通的函数参数形式,比如一个计算平方的函数:

def power(n):
	return n * n

这个 n 就是一个位置参数,直接调用 power(2) 即可。比如我们在定义一个函数来实现一个数的任意次方:

def power(x, n):
	s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

方法中两个位置参数 x, n,位置参数的意思就是你调用时对应的值要放到方法对应的参数的位置上。

默认参数

对于上面的 power 方法,实现第二种以后,原来调用第一种的代码就会出错来,怎么办呢?当然我们可以修改出错的代码,让其适应新代码,还有一种方法就是给参数 n 设置一个默认值:

def power(x, n=2):
	pass

这时我们在调用 power(5) 就相当于调用 power(5, 2),而对于 n 为其他值的情况还是要明确传入位置参数,比如: power(5, 3)

设置默认参数时,有几点要注意:

  • 一是必选参数在前,默认参数在后,否则Python的解释器会报错;
  • 二是当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

在有多个默认参数时,调用时也有一点要注意,比如定义一个如下方法:

def register(name, age=20, gender='male', city='beijing'):
	pass

如果调用是,只传第一个参数,那后面三个自然使用默认值,如果四个参数全传,自然也会使用传入的值来覆盖默认值,但是如果要中间两个想使用默认值,最后一个要传值怎么搞?当不按顺序提供部分默认参数时,需要把参数名写上,上面的方法可以这么调用:register('Tony', city='London'),而其他参数仍然使用默认值。

还有个默认参数的小坑,就是默认参数的值不能是可变对象,比如下面的方法:

def add_end(L=[]):
    L.append('END')
    return L

当你连续调用并使用默认值时,你会发现结果很诡异:

>>> add_end()
['END']
>>> add_end()
['END', 'END']

原因解释如下:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
所以要牢记 默认参数必须指向不变对象! 要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

可变参数 *

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
比如我们要计算多个数的求和,定一个接收多变参数的形式:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

在方法内部,这个 numbers 会被解析成一个 tuple,调用时就可以这样:

>>> calc(1)
1
>>> calc(1, 2)
5

如果已经有一个 list 或者 tuple,要调用一个可变参数怎么办?可以这样做:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

在 list 或 tuple 前面加一个 * 号,把 list 或 tuple 的元素变成可变参数传进去。这种写法非常有用,而且常用。

关键字参数 **

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入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, **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)

调用时,前面的 name 和 age 是位置参数,必须输入,后面的 city 和 job 是关键字参数,输入是必须指定关键字:

person('kaka', 100, '单身', city='上海', job='销售')

如果后面的 city 或 job 指定来默认值,调用时就可以不输入值了。

最后

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。