C++里函数可以设置缺省参数,Java不可以,只能通过重载的方式来实现,python里也可以设置默认参数,最大的好处就是降低函数难度,函数的定义只有一个,并且python是动态语言,在同一名称空间里不能有想多名称的函数,如果出现了,那么后出现的会覆盖前面的函数。
1
2
3
4
5
6
|
def power(x, n = 2 ): s = 1 while n > 0 : n = n - 1 s = s * x return s |
看看结果:
1
2
3
4
|
>>> power( 5 ) 25 >>> power( 5 , 3 ) 125 |
注意: 必选参数在前,默认参数在后,否则Python的解释器会报错。
建议:*当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。
默认参数也有坑,看看下面的代码,先定义一个list,添加一个end再返回:
1
2
3
|
def add_end(L = []): L.append( 'END' ) return L |
看看调用结果:
1
2
3
4
5
6
7
8
9
10
|
>>> 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' ] |
这里需要解释一下,Python函数在定义的时候,默认参数L的值就被计算出来了,即[]。此时L指向[]。所以如果L中的内容改变了,下次调用引用的内容也就不再是[]了。所以要牢记一点定义默认参数必须指向不可变对象!。
可变参数
第一种方法,传入的参数为一个list或者tuple。
1
2
3
4
5
|
def calc(numbers): sum = 0 for n in numbers: sum = sum + n * n return sum |
调用方式:
1
2
3
4
|
>>> calc([ 1 , 2 , 3 ]) 14 >>> calc(( 1 , 3 , 5 , 7 )) 84 |
第二种方式,直接传入多个参数,函数内部会自动用一个tuple接收。
1
2
3
4
5
|
def calc( * numbers): sum = 0 for n in numbers: sum = sum + n * n return sum |
调用方式:
1
2
3
4
|
>>> calc( 1 , 2 ) 5 >>> calc() 0 |
这个时候如果还想把一个list或者tuple里的数据传进去,可以这样:
1
2
3
|
>>> nums = [ 1 , 2 , 3 ] >>> calc( * nums) 14 |
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
1
2
|
def person(name, age, * * kw): print 'name:' , name, 'age:' , age, 'other:' , kw |
调用示例:
1
2
3
4
5
6
|
>>> 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' } |
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
递归函数
基本的也没什么可讲的,和Java/C++里一样,就是调用本身的一种。这里重点介绍一下尾递归优化。事实上尾递归和循环效果是一样的,很显然的一个优点那就是可以防止递归调用栈溢出。
定义:在函数返回的时候调用自身,并且,return语句不能包含表达式。编译器或者解释器可以对其做优化,无论调用多少次,只占用一个栈帧,不会出现溢出的情况。
举个简单的例子,以阶乘函数为例:
1
2
3
4
|
def fact(n): if n = = 1 : return 1 return n * fact(n - 1 ) |
如果传入的n很大,就可能会溢出,这是由于return n * fact(n - 1)引入了乘法表达式,就不是尾递归了。把代码改一下:
1
2
3
4
5
6
7
|
def fact(n): return fact_iter(n, 1 ) def fact_iter(num, product): if num = = 1 : return product return fact_iter(num - 1 , num * product) |
默认参数陷阱
Python的函数定义提供了默认参数这个选择,使得函数的定义和使用更加的灵活,但是也会带来一些坑,例如之前的一个例子:
函数定义:
1
2
3
|
def add_end(L = []): L.append( 'END' ) return L |
调用函数的结果:
1
2
3
4
5
6
7
8
9
10
|
>>> 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' ] |
很明显这个与函数的定义初衷不符,用一句话解释就是:
Default values are computed once, then re-used.
为了深入研究这个问题,我们来看看另一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# coding=utf-8 def a(): print "a executed" return [] def b(x = a()): print "id(x):" , id (x) x.append( 5 ) print "x:" , x for i in range ( 2 ): print "不带参数调用,使用默认参数" b() print b.__defaults__ print "id(b.__defaults__[0]):" , id (b.__defaults__[ 0 ]) for i in range ( 2 ): print "带参数调用,传入一个list" b( list ()) print b.__defaults__ print "id(b.__defaults__[0]):" , id (b.__defaults__[ 0 ]) |
NOTE:稍微解释一下,所有默认值都存储在函数对象的__defaults__属性中,这是一个列表,每一个元素均为一个默认参数值。
来看看输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
a executed 不带参数调用,使用默认参数 id(x): 140038854650552 x: [5] ([5],) id(b.__defaults__[0]): 140038854650552 不带参数调用,使用默认参数 id(x): 140038854650552 x: [5, 5] ([5, 5],) id(b.__defaults__[0]): 140038854650552 带参数调用,传入一个list id(x): 140038854732400 x: [5] ([5, 5],) id(b.__defaults__[0]): 140038854650552 带参数调用,传入一个list id(x): 140038854732472 x: [5] ([5, 5],) id(b.__defaults__[0]): 140038854650552 |
简单分析一下输出结果:
第1行
在定义函数b(),即执行def语句,代码第7行def b(x=a()):的时候,这句话使用了默认参数,所以在定义的时候会计算默认参数x的值,这个时候会调用a(),所以打印出了a executed。
第2~6行
第一次执行循环,代码第14行调用b()没有传递参数,使用默认参数,此时x=[],所以调用一次之后
1
|
print b.__defaults__ |
输出结果为
第7~11行
第二次循环,代码第14行调用b()没有传递参数,使用默认参数。
注意:默认参数只会计算一次,也就是说那个内存区域就固定了,但是这个地址所指向的是一个list,内容可以改变,此时由于上一次调用x: [5],所以
1
|
print b.__defaults__ |
输出结果为
1
|
([5, 5],) |
第12~16行
第二个循环语句,第一次循环,代码第20行传入一个空的list,所以不使用默认参数,此时x=[],所以
1
|
print b.__defaults__ |
输出结果为
第18~21行
第二个循环语句,第二次循环,代码第20行传入一个空的list,所以也不使用默认参数,此时仍然是x=[],所以
1
|
print b.__defaults__ |
输出结果依然为
函数也是对象,因此定义的时候就被执行,默认参数是函数的属性,它的值可能会随着函数被调用而改变。其他对象不都是如此吗?
牢记: 默认参数必须指向不变对象!代码改一下如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# coding=utf-8 def a(): print "a executed" return None def b(x = a()): print "id(x):" , id (x) if x is None : x = [] x.append( 5 ) print "x:" , x for i in range ( 2 ): print "不带参数调用,使用默认参数" b() print b.__defaults__ print "id(b.__defaults__[0]):" , id (b.__defaults__[ 0 ]) for i in range ( 2 ): print "带参数调用,传入一个list" b( list ()) print b.__defaults__ print "id(b.__defaults__[0]):" , id (b.__defaults__[ 0 ]) |
此时的输出结果看看是什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
a executed 不带参数调用,使用默认参数 id(x): 9568656 x: [5] (None,) id(b.__defaults__[0]): 9568656 不带参数调用,使用默认参数 id(x): 9568656 x: [5] (None,) id(b.__defaults__[0]): 9568656 带参数调用,传入一个list id(x): 140725126699632 x: [5] (None,) id(b.__defaults__[0]): 9568656 带参数调用,传入一个list id(x): 140725126699704 x: [5] (None,) id(b.__defaults__[0]): 9568656 |