第5章自定义功能单元(一)
5.1.1 声明函数
在Python中,使用def可以声明一个函数,完整的函数是由函数名、参数以及函数实现语句(函数体)组成的。同本书前面几章中所讲解的Python基本语句一样,在函数声明中,也要使用缩进以表示语句属于函数体。
如果函数有返回值,那么需要在函数中使用return语句返回计算结果,声明函数的一般形式如下。
def <函数名 > (参数列表):
<函数语句>
return <返回值>
其中参数列表和返回值不是必须的,return后也可以不跟返回值,甚至连return也没有。对于return后没有返回值的和没有return语句的函数都会返回None值。有些函数可能既不需要传递参数,也没有返回值。
注意:没有参数时,包含参数的圆括号也必须写上,圆括号后也必须有“:”。
在Python中,函数的参数除了上节中介绍一种方式之外,还可以有多种形式。例如,在调用某些函数,既可以向其传递参数,也可以不传递参数,函数依然可以正确调用;还有一些情况,比如函数中的参数数量不确定,可能为1个,也可能为几个、甚至几十个。对于这些函数,应该怎么定义其参数呢? \colorbox{yellow}{在Python中,函数的参数除了上节中介绍一种方式之外,还可以有多种形式。例如,在调用某些函数,既可以向其传递参数,也可以不传递参数,函数依然可以正确调用;还有一些情况,比如函数中的参数数量不确定,可能为1个,也可能为几个、甚至几十个。对于这些函数,应该怎么定义其参数呢?} 在Python中,函数的参数除了上节中介绍一种方式之外,还可以有多种形式。例如,在调用某些函数,既可以向其传递参数,也可以不传递参数,函数依然可以正确调用;还有一些情况,比如函数中的参数数量不确定,可能为1个,也可能为几个、甚至几十个。对于这些函数,应该怎么定义其参数呢?
5.2.1默认值参数
在Python中,可以在声明函数的时候,预先为参数设置一个默认值,当调用函数,如果某个参数具有默认值,则可以不向函数传递该参数,这时,函数将使用声明函数时为该参数设置的默认值来运行。声明一个参数具有默认值的函数形式如下。
def <函数名> (参数=默认值):
<语句>
>>> def hello (name='Python'):
... print('你好,%s' % name)
...
>>> print('无参数调用时的输出:')
无参数调用时的输出:
>>> hello()
你好,Python
>>> print('有参数("Jonson")调用时的输出:')
有参数("Jonson")调用时的输出:
>>> hello('Jonson')
你好,Jonson
>>>
如果声明一函数时,其参数列表中既包含无默认值参数,又包含有默认值的参数,那么在声明函数的参数时,必须先声明无默认值参数,后声明有默认值参数。请看以下在交互式环境的实验示例及错误提示:
>>> def test(e=3,a):
File "<stdin>", line 1
def test(e=3,a):
^
SyntaxError: non-default argument follows default argument
>>>
如果一个函数具有多个参数,而且这些参数都具有默认值,在调用函数的时候,可能仅想向最后一个参数传递值,该怎么办?【实例5-4】 演示了带有两个具有默认值参数的函数,代码如下:
>>> def hello (hi='你好',name='Python'):
... print('%s, %s!' % (hi,name))
...
>>> print('有一个参数("Jonson")调用时的输出:')
有一个参数("Jonson")调用时的输出:
>>> hello('Jonson')
Jonson, Python!
>>> print('有二个参数("hi","Jonson")调用时的输出:')
有二个参数("hi","Jonson")调用时的输出:
>>> hello('hi','Jonson')
hi, Jonson!
>>>
5.2.2参数传递
在Python中参数值的传递是按照声明函数时参数的位置顺序进行传递的,即位置参数,调用时提供的第一个参数值会被声明时的参数列表中的第一个参数获取,其他的参数结合方法依次类推。而实际上Python还提供了另外一种传递参数的方法——按照参数名传递值的方法,即提供关键字参数,提供关键字参数的函数调用类似于定义函数时设置参数的默认值。(???)
提供关键字参数调用函数时,要在调用函数名后的圆括号里写出形式为“关键字=参数值”的参数,这样,调用函数时提供参数就不必严格按照该函数声明时的参数列表的顺序来提供调用参数了。如果在函数调用时,既提供了关键字参数,又提供了位置参数,则位置参数会被优先使用,如果参数结合时出现重复,则会发生运行错误。注意:调用函数提供参数时,按顺序传递的参数要位于关键字参数之前,而且不能有重复的情况。
>>> def mult_test(a,b,c):
... return a*b*c
...
>>> mult_test(2,c=5,b=3)
30
>>> mult_test(c=5,b=3,2)
File "<stdin>", line 1
mult_test(c=5,b=3,2)
^
SyntaxError: positional argument follows keyword argument
>>> mult_test(2,c=5,b=3)
30
>>> mult_test(2,c=5,a=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: mult_test() got multiple values for argument 'a'
>>>
根据以上情况可知:一般来说,定义的函数如果既有无默认值的函数,又有有默认值的函数,应当把有默认值的参数放在后面。这样在调用函数时,前面的无默认值的参数就可以依次序取得调用时所给的值。对于默认值参数,没有依次取得时,就使用默认值,给函数调用带来很大的方便。
5.2.3可变数量参数传递
在自定义函数时,如果参数名前加上一个星号“*”,则表示该参数就是一个可变长参数。在调用该函数时,如果依次序将所有的其他变量都赋予值之后,剩下的参数将会收集在一个元组中,元组的名称就是前面带星号的参数名。
>>> def change_para_num(*tp1):
... print(type(tp1))
... print(tp1)
...
>>> change_para_num(1)
<class 'tuple'>
(1,)
>>> change_para_num(1,2,3)
<class 'tuple'>
(1, 2, 3)
>>>
【代码说明】代码中定义了一个函数change_para_num(*tpl),它只有一个带星号参数的函数。
【运行效果】如图所示,两次调用第一行输出类型为tuple,即元组;第一次调用时只有一个参数,元组中也只有一个值;第二次调用时有三个参数,全部收集到了tpl变量中。
当自定义函数时,参数中含有前面所介绍的三种类型的参数,则一般来说带星号的参数应放在最后。当带星号的参数放在最前面时,仍然可以正常工作,但调用时后面的参数必须以关键字参数方式提供,否则因其后的位置参数无法获取值而引发错误。
>>> def change_para_num(*tp1,a,b=0):
... print('tp1:',tp1)
... print('a:',a)
... print('b:',b)
...
>>> change_para_num(1,2,3,a=5)
tp1: (1, 2, 3)
a: 5
b: 0
>>> change_para_num(1,2,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: change_para_num() missing 1 required keyword-only argument: 'a'#该调用会出错,a变量没有默认值
>>>
【代码说明】代码中定义的函数change_para_num()有三种类型的参数,并且带星号的参数放在最前面。第一次调用时给出了3个位置参数和一个关键字参数,因此tpl变量收集了1、2、3作为一个元组,而变量a则取得值5,变量b则使用了默认值;第二次调用没有提供关键字参数,无默认值的参数a没有获取到值,所以调用失败。
使用元组来收集参数的参数时,调用时提供的参数不能为关键字参数,如果要收集不定数量的关键字参数可以在自定义函数时的参数前加两颗星即**valuename,这样一来,多余的关键字参数就可以以字典的方式被收集到变量valuename之中.
>>> def change_para_dict(a,b=0,**adct):
... print('adct:',adct)
... print('a:',a)
... print('b:',b)
...
>>> change_para_dict(1,k=3,b=2,c=3)
adct: {'k': 3, 'c': 3}
a: 1
b: 2
>>>
【代码说明】代码中定义了函数change_para_dct(),其最后一个参数前有两颗星号,即为收集关键字参数到字典中。调用时,给出两个多余的关键字参数,运行时会被放入名称为adct的字典中。这种收集字典的方式为函数中使用大量的默认值提供了方便,不用把大量的默认值全放在函数声明的参数中,而是把它放入程序中。
>>> def cube(name,**nature):
... all_nature={'x':1,
... 'y':1,
... 'z':1,
... 'color':'white',
... 'weight':1}
... all_nature.update(nature)
... print(name,"立方体的属性:")
... print('体积:',all_nature['x']*all_nature['y']*all_nature['z'])
... print('颜色:',all_nature['color'])
... print('重量:',all_nature['weight'])
...
>>> cube('first')
first 立方体的属性:
体积: 1
颜色: white
重量: 1
>>> cube('second',y=3,color='red')
second 立方体的属性:
体积: 3
颜色: red
重量: 1
>>> cube('third',z=2,color='green',weight=10)
third 立方体的属性:
体积: 2
颜色: green
重量: 10
>>>
【代码说明】代码中定义了一个函数cube(),其参数为两个,第一个是普通参数name,第二个为收集关键字参数。函数体中给了一个默认参数的字典,然后用字典的updae()函数将调用时提供的关键字参数更新至默认参数的字典。以后的应用中,直接从字典获取即可。最后用三种不同参数的方式调用cube函数。
5.2.4拆解序列的函数调用
前面使用函数调用时提供的参数都是位置参数和关键字参数,实际上调用函数时还可以把元组和字典进行拆解调用。
拆解元组提供位置参数;拆解字典提供关键字参数。调用时使用拆解元组的方法是在调用时提供的参数前加一个*号;要拆解字典必须在提供的调用参数前加两个*号。
>>> def mysum(a,b):
... return a+b
...
>>> print('拆解元组调用:')
拆解元组调用:
>>> print(mysum(*(3,4)))
7
>>> print('拆解字典调用:')
拆解字典调用:
>>> print(mysum(**{'a':3,'b':4}))
7
>>>
5.2.5函数调用时参数的传递方法
Python中的元素有可变和不可变之分,如整数、浮点数、字符串、元组等都属于不可变的;而列表和字典都属于可变的。
第3章中对元组是不可变的已经介绍过了,列表和字典的可变也是很好理解的,即它们可以增减元素、修改元素的值。那么整数、浮点数、字符串等为什么是不可变的呢?在本书的3.1.6节中介绍“=”时说,“=”号的作用是将对象引用与内存中某对象进行绑定,既然整数是不可变的,那么怎么改变一个指向整数的变量的值的呢?答案是直接在
内存中创建一个新的整数值,然后将变量引用与其绑定,这虽然本质上的其他高级语言不同,但在使用上是看不出什么差别的,但若将其提供给函数作为参数,则效果不同。
在函数调用时,若提供的是不可变参数,那么在函数内部对其修改时,在函数外部其值是不变的;若提供是可变参数,则在函数内部对它修改时,在函数外部其值也会改变的。
>>> def change(aint,alst):
... aint=0
... alst[0]=0
... alst.append(4)
... print('函数中aint:',aint)
... print('函数中alst:',alst)
...
>>> aint=3
>>> alst=[1,2,3]
>>> print('调用前aint:',aint)
调用前aint: 3
>>> print('调用前alst:',alst)
调用前alst: [1, 2, 3]
>>> change(aint,alst)
函数中aint: 0
函数中alst: [0, 2, 3, 4]
>>> print('调用后aint:',aint)
调用后aint: 3
>>> print('调用后alst:',alst)
调用后alst: [0, 2, 3, 4]
>>>
【代码说明】代码中定义了一个修改提供参数的函数change(),参数包括一个整数和一个列表。在调用前定义并输出了一个整数aint和一个列表alst,然后把他们作为参数调用change(),最后输出两个变量的值。
【运行效果】如上所示,在调用前后不可变量aint的值虽然在函数内部进行了改变,但函数退出后其值仍然不变;而可变的列表alst则完全不同,在调用函数后,显示了函数内的一切改变都应用了。但是在函数中,可变的和不可变的量的值的改变是应用的。列表、字典是可变对象,它在作为函数的默认参数时要注意一个“陷阱”。
>>> def myfun(lst=[]):
... lst.append('abc')
... print(lst)
...
>>> myfun()
['abc']
>>> myfun()
['abc', 'abc']
>>> myfun()
['abc', 'abc', 'abc']
>>>
【代码说明】代码中只定义了一个带有空列表默认参数的函数myfun(),然后在不提供参数的情况下调用了三次这个函数。
【运行效果】每次调用函数按默认值的理解,应该每次传入空列表,列表中只有一个元素’abc’,但事实不是如此,结果如上所示:
>>> def myfun(lst=None):
... lst=[] if lst is None else lst
... lst.append('abc')
... print(lst)
...
>>> myfun()
['abc']
>>> myfun()
['abc']
>>> myfun()
['abc']
>>>
5.3变量的作用域
在Python中,作用域可以分为:
内置作用域:Python预先定义的;
全局作用域:所编写的整个程序;
局部作用域:某个函数内部范围。
每次执行函数,都会创建一个新的命名空间,这个新的命名空间就是局部作用域,同一函数不同时间运行,其作用域是独立的,不同的函数也可以具有相同的参数名,其作用域也是独立的。在函数内已经声明的变量名,在函数以外依然可以使用。并且在程序运行的过程中,其值并不相互影响。
>>> def myfun():
... a=0
... a+=3
... print('函数内a:',a)
...
>>> a='external'
>>> print('全局作用域a:',a)
全局作用域a: external
>>> myfun()
函数内a: 3
>>> print('全局作用域a:',a)
全局作用域a: external
>>>
【代码说明】代码中在函数中声明了变量a,其值为整数类型;在函数外声明了同名变量a,其值为字符串。在调用函数前后,函数外声明的变量a的值不变。在函数内可以对a的值进行任意操作,它们互不影响。如果将全局作用域中的变量作为函数的参数引用,则是不同的,但这两者不属于同一问题范畴。但是,还有一种方法使函数中引用全局变量并进行操作,如果要在函数中使用函数外的变量,可以在变量名前使用global关键字。
>>> def myfun():
... global a
... a=0
... a+=3
... print('函数内a:',a)
...
>>> a='external'
>>> print('全局作用域a:',a)
全局作用域a: external
>>> myfun()
函数内a: 3
>>> print('全局作用域a:',a)
全局作用域a: 3
>>>
【代码说明】示例代码仅在函数myfun()声明中增加了一句,就使函数内使用的变量a成为全局变量。
【运行效果】如上所示,在函数改变了全局作用域变量a的值,即由字符串‘external’变为整数3。
>>> a=3
>>> def myprint():
... print(a)
...
>>> myprint()
3
>>> a=3
>>> def myprint():
... print(a)
... a=5
...
>>> myprint()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in myprint
UnboundLocalError: local variable 'a' referenced before assignment
>>>
以上代码引发的错误是局部变量在分配前不能引用,原因是与Python中的变量查找有关,在此外代码中函数内声明了a变量并初始化,所以a被判为局部变量,但却之前在print(a)中引用了它。
5.4使用匿名函数(lambda)
lambda可以用来创建匿名函数,也可以将匿名函数赋给一个变量供调用,它是Python中一类比较特殊的声明函数的方式,lambda来源于
LISP语言,其语法形式如下:
lambda params:expr
其中params相当于声明函数时的参数列表中用逗号分隔的参数,expr是函数要返回值的表达式,而表达式中不能包含其他语句,也可以返回元组(要用括号),还允许在表达式中调用其他函数。
>>> import math
>>> s=lamda x1,y1,x2,y2:math.sqrt((x1-x2)**2+(y1-y2)**2)
File "<stdin>", line 1
s=lamda x1,y1,x2,y2:math.sqrt((x1-x2)**2+(y1-y2)**2)
^
SyntaxError: invalid syntax
>>> s=lambda x1,y1,x2,y2:math.sqrt((x1-x2)**2+(y1-y2)**2)
>>> s(1,1,0,0)
1.4142135623730951
>>>
lambda一般用来定义以下类型的函数:
简单匿名函数 写起来快速而简单,省代码;不复用的函数 在有些时候需要一个抽象简单的功能,又不想单独定义一个函数;为了代码清晰有些地方使用它,代码更清晰易懂。比如在某个函数中需要一个重复使用的表达式,就可以使用lambda来定义一个匿名函数,多次调用时,就可以少写代码,但条理清晰。
5.5Python常用内建函数
>>> help(abs)
>>> bin(20)
'0b10100'
>>> hex(20)
'0x14'
>>> oct(20)
'0o24'
>>> callable(aa)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'aa' is not defined
>>> callable(abs)
True
>>> aa=3
>>> callable(aa)
False
>>> chr(97)
'a'
>>> ord('k')
107
>>> alst=[0,1,2,3,4]
>>> list(filter(lambda x:x%2,alst))
[1, 3]
>>> list(map(lambda x:2*x,alst))
[0, 2, 4, 6, 8]
>>> isinstance('abc',str)
True
>>>
以上代码中,list(map(lambda x:2x,alst))中的lambda x:2x,就是用lambda定义的一个匿名函数,并通过map()函数,将其应用到alst列表中的每个元素。
>>> def myfun():
... pass
...
>>> myfun()
>>>
>>> def output(outputs):
... for item in outputs:
... print(item)
...
>>> output([1,2,3])
1
2
3
>>> output('abcd')
a
b
c
d
>>> output(3.4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in output
TypeError: 'float' object is not iterable
>>> output((1,2,3))
1
2
3
>>>
>>> def testfun(a,b=3)
File "<stdin>", line 1
def testfun(a,b=3)
^
SyntaxError: invalid syntax
>>> def testfun(a,b=3):
... pass
...
>>> testfun(1,2)
>>> testfun(1,2)
>>> testfun(1,3)
>>> test(b=3,a=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined
>>> testfun(b=3,a=1)
>>> testfun(a=1)
>>>
>>> def test(a,b=0,*kw,**kwargs):
... pass
...
>>> test(3)
>>> test(3,4,5)
>>> test(3,c=3)
>>> test(c=3,3)
File "<stdin>", line 1
test(c=3,3)
^
SyntaxError: positional argument follows keyword argument
>>>
>>> seq = [4,5,6,7,8,9]
>>> seq = filter(lambda x:x%3 and x%2,seq)
>>> seq
<filter object at 0x7fce92b01af0>
>>> print(seq)
<filter object at 0x7fce92b01af0>
>>> for i in seq:
... print(seq)
...
<filter object at 0x7fce92b01af0>
<filter object at 0x7fce92b01af0>
>>>