3.3.2 函数参数不得不说的几件事
函数定义时圆括号内是使用逗号分隔开的形式参数列表(parameters),一个函数可以没有参数,但是定义和调用时一对圆括号必须要有,表示这是一个函数并且不接受参数。函数调用时向其传递实参(arguments),根据不同的参数类型,将实参的值或引用传递给形参。
定义函数时,对参数个数并没有限制,如果有多个形参,则需要使用逗号进行分隔。例如,下面的函数用来接受2个参数,并输出其中的最大值。
1 def printMax(a,b):
2 if a > b :
3 print(a,'is the max')
4 else:
5 print(b,'is the max')
注意:这里只是为了,演示,忽略了一些细节,如果输入的参数不支持比较运算符,则会出错,可以参考后面第7章中介绍的异常结构来解决这个问题。
对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参。例如:
1 def addOne(a):
2 print(a) #输出原变量 a 的值
3 a += 1
4 print(a) #这条语句会得到一个新的变量a
5
6 a = 3
7 addOne(3)
8
9 # 3
10 # 4
11
12 print(a)
13 # 3
从运行结果可以看出,在函数内部修改了形参a的值,但是当函数运行结束以后,实参a的值并没有被修改。然而,在有些情况下,可以通过特殊的方式在函数内部修改实参的值,例如:
1 >>> def modify(v): #修改列表元素值
2 v[0] += 1
3
4
5 >>> a = [2]
6 >>> modify(a)
7 >>> a
8 [3]
9 >>>
10 >>>
11 >>> def modify(v,item): #为列表增加元素
12 v.append(item)
13
14
15 >>> a = [2]
16 >>> modify(a,3)
17 >>> a
18 [2, 3]
19 >>>
20 >>>
21 >>>
22 >>> def modify(d): #修改字典元素值或为字典增加元素
23 d['age'] = 38
24
25
26 >>> a = {'name':'Dong','age':37,'sex':'Male'}
27 >>>
28 >>> modify(a)
29 >>>
30 >>> a
31 {'name': 'Dong', 'sex': 'Male', 'age': 38}
32 >>>
也就是说,如果传递给函数的是Python可变序列,并且在函数内部使用下标或序列自身支持的方式为可变序列增加、删除元素或修改元素值时,修改后的结果是可以反映到函数之外的,即实参也得到了相应的修改。
1 默认值参数
在定义函数时,Python支持默认参数,即在定义函数时为形参设置默认值。在调用带有默认值参数的函数时,可以不用为设置了默认值的参数进行传值,此时函数将会直接使用函数定义时设置的默认值,也可以通过显式赋值来替换其默认值。也就是说,在调用函数时是否为默认值参数传递实参是可选的,具有较大的灵活性。带有默认值参数的函数定义语法如下:
1 def 函数名(...,形参名 = 默认值):
2 函数体
可以使用 函数名.__defaults__ 随时查看函数所有默认值参数的当前值,其返回值为一个元组,其中的元素依次表示每个默认值参数的当前值。
1 >>> def say(message,times = 1): 2 print((message + ' ')* times) 3 4 5 >>> 6 >>> 7 >>> dir(say) 8 ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__',
'__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] 9 >>> 10 >>> say.__defaults__ 11 (1,) 12 >>>
调用该函数时,如果只为第一个参数传递实参,则第二个参数使用默认值 1 ;如果为第二个参数传递参数,则不再使用默认值 1 ,而是使用调用者显式传递的值。
1 >>> say('hello')
2 hello
3 >>>
4 >>> say('hello',3)
5 hello hello hello
6 >>>
7 >>> say('hi',5)
8 hi hi hi hi hi
9 >>>
注意:在定义带有默认值参数的函数时,默认值参数必须出现在函数参数列表的最右端,任何一个默认值参数右边都不能再出现非默认值参数。
注意:一般情况下,都是调用函数时为其传递参数,这时形参的值由调用函数时实参的值确定。但如果函数的默认值参数不是调用时传递的,而是通过其他方式对其赋值,那么默认值参数的值可能会在函数定义是确定,而不是函数调用时。例如:
1 >>> i = 5
2 >>> def demo(v):
3 print(v)
4
5
6 >>> i = 6
7 >>> demo(i)
8 6
9 >>>
10 >>> i = 5
11 >>>
12
13 >>> def demo(v = i): #等价于def demo(v = 5):
14 print(v) #相当于定义函数时设置默认值参数 5
15
16
17 >>> i = 6
18 >>>
19 >>> demo()
20 5
21 >>>
注意:多次调用函数并且不为默认值参数传递值时,默认值参数只在第一次调用时进行解释,对于列表,字典这样可变类型的默认值参数,这一点可能会导致很严重的逻辑错误,而这种错误或许会耗费大量精力来定位和纠正。例如:
1 >>>
2 >>> def demo(newitem,old_list=[]):
3 old_list.append(newitem)
4 return old_list
5
6 >>>
7 >>> print(demo('5',[1,2,3,4]))
8 [1, 2, 3, 4, '5']
9 >>>
10 >>> print(demo('aaa',['a','b']))
11 ['a', 'b', 'aaa']
12 >>>
13 >>> print(demo('a'))
14 ['a']
15 >>>
16 >>> print(demo('b'))
17 ['a', 'b']
18 >>>
19 >>> print(demo('c'))
20 ['a', 'b', 'c']
21 >>>
上面的函数使用列表作为默认值参数,由于其可记忆性,连续多次调用该函数而不给该参数传值,再次调用时将保留上一次调用的结果,从而导致很难发现的错误。下面的代码就不存在这个问题:
1 >>>
2 >>> def demo(newitem,old_list=None):
3 if old_list is None:
4 old_list = []
5 old_list.append(newitem)
6 return old_list
7
8 >>>
2 关键字参数
关键字参数主要指调用函数时的参数传递方式,与函数定义无关。通过关键字参数可以按参数名字传递值,实参顺序可以和形参顺序不一致,但不影响参数值的传递结果,避免了用户需要牢记参数位置和顺序的麻烦,使得函数的调用和参数传递更加灵活方便。
1 >>>
2 >>> def demo(newitem,old_list=None):
3 if old_list is None:
4 old_list = []
5 old_list.append(newitem)
6 return old_list
7
8 >>>
9 >>>
10 >>>
11 >>>
12 >>> def demo(a,b,c = 5):
13 print(a,b,c)
14
15
16 >>>
17 >>> demo(3,7)
18 3 7 5
19 >>>
20 >>> demo(a=7,b=3,c=6)
21 7 3 6
22 >>>
23 >>> demo(c=8,a=9,b=0)
24 9 0 8
25 >>>
3 可变长度参数
可变长度参数在定义函数时主要有两种形式:*parameter 和 **parameter,前者用于接受任意多个实参并将其放在一个元组中,后者接受类似于关键字参数一样显式赋值形式的多个实参并将其放入字典中。
下面代码演示了第一种形式可变长度参数的用法,即无论调用该函数时传递了多少实参,一律将其放入元组中:
1 >>>
2 >>> def demo(*p):
3 print(p)
4
5
6 >>> demo(1,2,3)
7 (1, 2, 3)
8 >>>
9 >>> #其实 1,2,3 本身就是元组
10 >>> a = 1,2,3
11 >>> type(a)
12 <class 'tuple'>
13 >>>
14 >>> demo(1,2,3,4,5,6,7)
15 (1, 2, 3, 4, 5, 6, 7)
16 >>>
下面的代码演示了第二种形式可变长度参数的用法,即在调用函数时自动将接收的参数转换为字典:
1 >>>
2 >>> def demo(**p):
3 for item in p.items():
4 print(item)
5
6
7 >>> demo(x = 1,y = 2,z = 3)
8 ('y', 2)
9 ('z', 3)
10 ('x', 1)
11 >>>
注意:Python定义函数时可以同时使用位置参数、关键字参数、默认值参数和可变长度参数,但是除非真的很必要,否则请不要这样用,因为这会使得代码非常混乱而严重降低可读性,并导致程序查错非常困难。另外,一般而言,一个函数如果可以接受很多不同类型参数的话,很可能是函数设计得不好,例如函数功能过多,需要进行必要的拆分和重新设计,以满足模块高内聚的要求。
4 传递参数时的序列解包
调用含有多个参数的函数时,可以使用Python列表、元组、集合、字典以及其他可迭代对象作为实参,并在实参名称前加一个星号,Python解释器将自动进行解包,然后传递给多个单变量形参。
1 >>>
2 >>> seq = [1,2,3]
3 >>>
4 >>> demo(*seq)
5 6
6 >>>
7 >>> tup = (1,2,3)
8 >>>
9 >>> demo(*tup)
10 6
11 >>>
12 >>>
13 >>> dic = {1:'a',2:'b',3:'c'}
14 >>> demo(*dic)
15 6
16 >>>
17 >>> set = {1,2,3}
18 >>> demo(*set)
19 6
20 >>>
21 >>> demo(*dic.values())
22 abc
23 >>>
24 >>> demo(*dic.keys())
25 6
26 >>>
27 >>> demo(*dic.items())
28 (1, 'a', 2, 'b', 3, 'c')
29 >>>
小提示:
(1)字典对象作为实参时默认使用字典的“键”,如果需要将字典中的“键:值”元素作为参数则需要使用items()方法明确说明,如果需要将字典的“值”作为参数则需要调用字典的values()方法明确说明;
(2)实参中元素个数与形参个数必须相等,否则将出现错误。
注意:调用函数时如果对实参使用一个星号(*)进行序列解包,那么这些解包后的实参将会被当做普通位置参数对待,并且会在关键参数和使用两个星号(**)进行序列解包的参数之前进行处理。
1 >>>
2 >>> def demo(a,b,c): #定义函数
3 print(a,b,c)
4
5
6 >>>
7 >>> demo(*(1,2,3)) #调用,序列解包
8 1 2 3
9 >>>
10 >>> demo(1,*(2,3)) #位置参数和序列解包同时使用
11 1 2 3
12 >>>
13 >>> demo(1,*(2,),3)
14 1 2 3
15 >>>
16 >>>
17 >>> demo(a = 1,*(2,3)) #序列解包相当于位置参数,优先处理
18 Traceback (most recent call last):
19 File "<pyshell#47>", line 1, in <module>
20 demo(a = 1,*(2,3)) #序列解包相当于位置参数,优先处理
21 TypeError: demo() got multiple values for argument 'a'
22 >>>
23 >>> demo(b = 1,*(2,3))
24 Traceback (most recent call last):
25 File "<pyshell#49>", line 1, in <module>
26 demo(b = 1,*(2,3))
27 TypeError: demo() got multiple values for argument 'b'
28 >>>
29 >>>
30 >>> demo(c = 1,*(2,3))
31 2 3 1
32 >>>
33 >>>
34 >>>
35 >>>
36 >>> demo(**{'a':1,'b':2},*(3,)) #序列解包相当于位置参数,优先处理
37 SyntaxError: iterable argument unpacking follows keyword argument unpacking
38 >>>
39 >>>
40 >>>
41 >>>
42 >>>
43 >>> demo(*(3,),**{'a':1,'b':2})
44 SyntaxError: invalid character in identifier
45 >>>
46 >>> demo(*(3,),**{'a':1,'b':2})
47 Traceback (most recent call last):
48 File "<pyshell#72>", line 1, in <module>
49 demo(*(3,),**{'a':1,'b':2})
50 TypeError: demo() got multiple values for argument 'a'
51 >>>
52 >>>
53 >>> demo(*(3,),**{'c':1,'b':2})
54 3 2 1
55 >>>