Python3程序设计实战教程第5讲 函 数_第1页
Python3程序设计实战教程第5讲 函 数_第2页
Python3程序设计实战教程第5讲 函 数_第3页
Python3程序设计实战教程第5讲 函 数_第4页
Python3程序设计实战教程第5讲 函 数_第5页
已阅读5页,还剩63页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第5讲

函数

导入到目前为止,所编写的代码大部分都是以一个代码块的形式出现,当某个功能需要在程序中的不同位置重复执行时,只能将代码块不断的复制、粘贴,这样不仅代码重复率高,而且使后期的维护变得困难。我们必须要找到一个方法来解决这个问题,这个方法就是使用函数。函数是程序模块化的产物,使用函数能有效提高程序的可靠性,可测试性、可维护性和正确性,无论在哪门变成语言中,函数都扮演者至关重要的角色。主要内容掌握函数的定义及调用掌握参数传递的过程掌握递归函数的定义及调用熟悉变量的作用域熟悉lambda函数了解闭包及装饰器了解导入模块及自定义模块的方法5.1.1

函数的作用通常一个程序需要的功能非常多,如果把所有功能放在一起,写成一长串代码,不仅可读性会变差,而且也不利于程序员之间的协作,因此我们常常将程序分解成小的功能独立的片段,即函数。使用函数有以下几个好处:1.更简洁的代码程序代码在被分解为函数后,功能更加明确,可读性更强。2.代码重用函数也能有效减少程序中重复代码。如果在程序中存在多处执行相同的操作,那么我们可以把这个操作定义成函数,以后在需要时直接调用这个函数就行了。3.便于测试通常一个函数就是一个功能,程序员能够单独测试程序中的每个函数,以确定功能是否正确执行。4.便于团队合作函数也使得团队协作变得容易。将程序分解为一组完成特定功能的函数,不同程序员承担编写不同函数的任务,最后通过函数调用,就可以完成程序的开发,增加开发效率。5.1.2定义函数定义函数也被称作函数声明,基本语法格式如下:

def函数名([形参列表]):

"文档字符串"

函数体说明:1、第一行俗称函数头,标志着函数定义的开始。函数头以关键字def开始,后面跟着函数名,一对圆括号和冒号。2、函数名为有效的标识符,一般情况下首字母不要大写,以区别后面讲到的类,函数名应该具有一定的描述性,使得任何阅读代码的人能够合理的猜测函数的功能。3、括号中的形式参数,简称形参,可以有多个,也可以没有。当有多个参数时,参数与参数之间用逗号进行分割。需要注意的是,即使这个函数没有参数,圆括号也不能省略。4、文档字符串是对函数功能、参数和使用方法必要的解释,放在函数体前面,可以使用help函数进行查询。需要注意的是,文档字符串不是必需的,但是加上一段可以为函数调用者提供友好的提示和使用帮助。5、文档字符串和函数体相对def关键字必须保持一定的空格缩进。6、在函数体中使用return[表达式]语句将表达式的值返回给调用方,如果没有return语句则返回值为None。5.1.2定义函数【例5-1】定义一个空函数,代码如下:

defnothing(): pass说明:1.定义了一个名字为nothing的函数2.该函数没有形式参数3.

函数体中的pass语句是空语句,一般用作占位符来保证程序结构的完整性。当定义函数时,对函数体的实现还不是很明确,可以使用pass来占位,保证程序结构完整性,以确保其他代码能够继续运行。函数体中没有return语句,函数的返回值为None。【例5-2】定义一个函数,输出”HelloWorld!”,代码如下:

defhello(): print("HelloWorld!")说明:1.定义了一个名字为hello的函数2.该函数没有形式参数3.

函数体为print("HelloWorld!"),输出“hello

World”字符串。函数体中没有return语句,函数的返回值为None。5.1.2定义函数【例5-3】定义一个函数,返回n!的值代码如下:

deffactorial(n): s=1 foriinrange(n): s=s*(i+1) returns说明:1.定义了一个名字为factorial的函数。2.该函数有形式参数n。3.函数体实现了求n!的操作,并把n!的值存在s中,最后返回s的值(return

s)。定义一个函数,求三个数之和,代码如下:

defmy_sum(a,b,c):

"输入三个数,返回三个数之和"#文档字符串 returna+b+c说明:1.定义了一个名字为my_sum的函数。2.该函数有形式参数a,

b,

c。3.

"输入三个数,返回三个数之和"为文档字符串,描述了该函数的使用方法及功能。4.

函数体为返回a+b+c的值。5.1.3调用函数函数定义后,如果不调用函数,那么这些代码就不会被执行。要执行一个函数,必须调用它。函数的调用,需要根据函数定义,给定实际传入的参数值,函数调用的语法格式如下:

函数名([实际参数列表])说明:1、在python中,函数的使用有严格的规定,函数不允许向前引用,即函数的调用应当在函数定义之后。2、实参列表必须与函数定义的形参列表一一对应。3、函数调用时表达式。如果函数有返回值,则可以在表达式中调用函数;如果没有返回值,则可以将函数单独作为表达式语句使用。4、当调用一个函数时,程序控制权就会转移到被调用的函数上;当被调用的函数执行完时,程序控制权就会回到调用者。调用者叫做主调函数,被调用的函数叫做被调函数。5、函数的调用既可以在交互模式下进行,也可以把函数的定义和调用都放在一个程序文件中。5.1.2定义函数【例5-5】在交互模式下定义并调用函数。

>>>defmy_sum(a,b,c): "输入三个数,返回三个数之和" returna+b+c

>>>print(my_sum(1,2,3))

6

>>>help(my_sum)

Helponfunctionmy_suminmodule__main__:

my_sum(a,b,c)

输入三个数,返回三个数之和说明:1.定义了一个名字为my_sum的函数。2.该函数有形式参数a,

b,

c。3.

"输入三个数,返回三个数之和"为文档字符串,描述了该函数的使用方法及功能。4.

函数体为返回a+b+c的值。5.输出调用函数my_sum的结果,其中实参为1,2,3,即输出1,2,3的和,输出6。6.使用help函数查询my_sum函数中的文档字符串5.1.2定义函数【例5-5】在程序文件中定义并调用函数。

先新建一个python文件my_sum.py,并输入代码,代码如下:

defmy_sum(a,b,c): "输入三个数,返回三个数之和" returna+b+c

print(my_sum(1,2,3))

保存运行该程序文件,在“开始”中打开cmd,进入命令行模式后,运行代码,运行结果如下:

python3my_sum.py

6说明:1.

python3my_sum.py,如果想直接使用python3命令运行,那么python的安装路径必须已经添加到环境变量,否则需要进入到pyhton的安装目录中才能使用该命令。2.

my_sum.py,如果程序文件保存在python的同级目录下则可以写相对地址,否则在调用时应写绝对地址才能调用成功。5.2函数的参数在定义函数时括号里的参数称为形式参数(简称形参),调用函数时通过括号里的参数称为实际参数(简称实参)。实际参数引用默认按位置顺序依次传递给形式参数。如果参数个数、类型、顺序不对,则会产生错误。5.2.1参数形式

1.位置参数

函数调用时,实参的顺序、个数必须与函数定义中形参的顺序、个数一致,一个萝卜一个坑。【例5-7】使用位置参数传递实参示例。

>>>deflocation(a,b): print(a) print(b)

>>>location(3,4)

3

4

函数调用时,按照顺序传递参数,将3传给a,4传给b。5.2.1参数形式2.关键字参数

在函数调用时,通过参数名=参数值的形式输入实参。使用关键字参数允许函数调用时的参数顺序与函数定义时的不一致,因为python解释器能够通过参数名匹配参数值。【例5-8】使用关键字参数传递实参示例。

>>>deflocation(a,b): print(a) print(b)

>>>location(b=3,a=4)

4

3

相同的函数定义,但在函数调用时,通过关键字传递参数,将3传给b,4传给a。5.2.1参数形式3.默认参数

当有些参数在函数定义中定义了默认值,如果在函数调用时没有输入这些参数,则使用函数定义中的默认值,如果输入了这些参数,则会用输入值覆盖默认值。需要注意的是,在定义这种参数时,必须放在形参列表的末尾。【例5-9】使用默认参数示例。

>>>defdefualt(a,b=4): print(a) print(b)

>>>defualt(3)#调用函数时不传入b的值

3

4#b为默认值4

>>>defualt(3,5)#调用函数时传入b的值

3

5#b的值为传入的值5.2.1参数形式4.不定长参数

当我们在函数定义时定义了不定长形参,那么在函数调用时,则会将多余的参数用元组或者字典的形式组织起来。在形参名前加一个*号,则以元组的形式组织;加两个*号,则以字典的形式组织。一般在给不定长参数命名时,习惯用*args和**kwargs来命名。【例5-10】使用元组来存放不定长参数。

>>>defdemo_1(a,*args):#定义了一个形参a,和一个不定长参数args。 print(a)#输出a的值 print(args)#输出args的值。

>>>demo_1(1,2,3,4)#传入实参

1#a的值

(2,3,4)#args的值

demo_1函数中有两个参数,其中1传给了a,其余的参数都给了args,并以元组的方式组织。5.2.1参数形式【例5-11】使用字典来存放不定长参数。>>>defdemo_2(a,**kwargs):#定义了一个形参a,和一个不定长参数kwargs。 print(a)#输出a的值 print(kwargs)#输出kwargs的值。>>>demo_2(1,b=2,c=3,d=4)1#a的值{'b':2,'c':3,'d':4}#kwargs的值demo_2函数中有两个参数,其中1传给了a,其余的参数都给了kwargs,并以字典的方式组织。需要注意的是,当不定长参数为**kwargs时,传入该实参的方式也需改变成参数名=参数值。5.2.2参数传递时的序列解包

为含有多个变量的函数传递参数时,可以使用python列表、元组、字典、集合以及其他可迭代对象作为实参,为了能够正确赋值,我们需要通过“*”或“**”进行解包,然后传递给形参变量。【例5-12】使用“*”解包,传递可迭代对象。>>>defdemo_2(a,b,c): print("%s,%s,%s"%(a,b,c))>>>l=[1,2,3]>>>demo_2(*l)1,2,3在传参时,在l前面加了一个“*”号,表示对l列表解包,解包后将1、2、3分别传给了a、b、c。5.2.2参数传递时的序列解包

在解包元组、列表等对象时我们用“*”在解包字典时,我们使用“**”。【例5-14】使用“**”解包,传递字典对象。>>>defdemo_3(a,b,c): print("%s,%s,%s"%(a,b,c))>>>d={"a":1,"b":2,"c":3}>>>demo_3(**d)1,2,3这里需要注意的是,字典是关键字参数,将字典解包后,键作为参数名,值作为参数值,所以字典中的键必须是和函数形参是一致的。

5.2.3参数的传递

在了解函数的参数传递之前,我们必须先要了解python中变量的一些特性。在python中,类型是属于对象的,变量是没有类型的,因此最好把他们理解为附加在对象上的标注,所以在python中就允许有这样的代码:【例5-14】同一个变量绑定到不同类型的对象。>>>a=1>>>print(type(a))<class'int'>>>>a=[1,2,3]>>>print(type(a))<class'list’>因为变量a没有类型,仅仅是对象的引用,所以a既能引用int对象也能引用list对象,就像便利贴一样,可以贴在int对象上,也能贴在list对象上。python中的赋值语句,应该始终先读右边,对象在右边创建或获取,之后左边的变量才会绑定在对象上,就像为对象贴标注。5.2.3参数的传递在python中又可以把对象分成如int、str、bool、tuple等不可变对象和如list、dict等可变对象。【例5-16】不可变对象特性示例。>>>a=1#创建int对象1,并将变量a绑定到该对象上。>>>b=a#获取int对象1,并将变量b绑定到该对象上。>>>print(id(a)) #id函数,返回对象的唯一标识符。4446951728>>>print(id(b))4446951728>>>b=2>>>print(id(a))4446951728>>>print(id(b))4446951760说明:1.

赋值语句a=1会创建int数据对象1和变量a,然后将变量a指向值1对象,之后又令b=a,其此时a和b都指向同一个对象,所以id(a)、id(b)、这两个值一样。

2.语句b=2对b进行了重新赋值,由于int是不可变对象,它没有办法将int对象1改为2,所以实际上会新生成一个int对象2,并让b引用它,id(b)则发生变化。由于a还是引用int对象1,所以id(a)没发生变化。5.2.3参数的传递【例5-17】可变对象特性示例。>>>a=[1,2,3]>>>b=a>>>print(id(a))4591669376>>>print(id(b))4591669376>>>b.append(4)#通过append函数改变列表的值,在列表末端增加元素4。>>>print(b)[1,2,3,4]#列表元素发生变化。>>>print(id(b))4591669376>>>print(id(a))4591669376>>>print(a)[1,2,3,4]说明:1.我们对a变量赋值,先创建list对象[1,2,3]并将a绑定在该对象上,之后又令b=a,其此时a和b都指代同一个对象,所以id(a)、id(b)这两个值都一样。2.

之后通过append函数,改变list对象的值,由于list是可变对象,不会创建一个新的list对象而是会在原有的list对象上进行改变,添加上一个新元素4,所以id(b)的值并没有发生变化。在列表对象发生变化之后,a依旧绑定着这个列表对象(即id(a)的值没有发生变化),所以a也变成了[1,2,3,4]。5.2.3参数的传递

现在我们来开始学习python的传参模式。python唯一支持的参数传递模式是共享传参(callbysharing)。共享传参指函数的各个形式参数获得实参中各个引用的副本,也就是说,函数内部的形参是实参的别名。1.实参为不可变对象【例5-19】定义一个函数,改变形参的值,观察实参变化情况。>>>defchange_var_1(a): print(id(a))#输出a的id值 print(a)#输出a的id值 a=a+1#改变a的值 print(id(a))#输出改变值后的a的id值 print(a)#输出改变值后的a的值>>>b=10>>>print(id(b))4446952016>>>change_var_1(b)#调用函数444695201610444695204811>>>print(b)10>>>printf(id(b))44469520165.2.3参数的传递说明1.

当形参a没有发生变化时,他的id值和实参b一样2.

通过a=a+1这条语句改变int对象的值时,由于int对象时不可变对象,此时新生成了一个int对象11

,并让a引用它,所以id(a)的值发生了变化,而实参b依旧指代int对象10,所以实参的值并没有发生变化。3.

总结:当实参为不可变对象时,形参值的改变并不会影响实参值。这种行为特征与C语言中的值传递相似。5.2.3参数的传递2.实参为可变对象在调用函数时,其实也是做了相同的赋值操作即形参=实参,实参引用的是可变对象(例如list、dict等对象)时,如果在函数体中修改对象的值,实际上是在原有的对象上进行修改,从而会改变实参的值。【例5-21】定义一个函数,改变形参的值,观察实参变化情况。>>>defchange_var_2(a): print(id(a))#输出a的id值 print(a)#输出a的值 a.append(4)#改变a的值,在末尾添加一个元素4 print(id(a))#输出改变值后的a的id值 print(a)#输出改变值后的a的值>>>b=[1,2,3]>>>print(id(b))4428513088>>>change_var_2(b)#调用函数4428513088[1,2,3]4428513088[1,2,3,4]>>>print(id(b))4428513088>>>print(b)[1,2,3,4]5.2.3参数的传递说明1.开始调用函数,将实参传递给形参,此时a、b指代的是同一个列表对象,a的id值和实参b也就一样。2.之后通过a.append(4)这条语句改变list对象的值时,由于list对象是可变对象,它在原有的list对象基础上增加了一个元素4,所以id(a)的值并不会发生变化,而实参b也依旧引用这个list对象,所以实参的值也跟着发生变化。3.

总结:当实参为可变对象时,形参的改变也会作用于实参。这种行为特征与C语言中的地址传递相似。

5.3.1return语句与函数的返回值

函数的值通过return语句返回主调函数。return语句的一般形式为:return表达式说明1.该语句的功能是计算表达式的值,并返回给主调函数。2.在一个函数中允许有多个return语句,但每次调用只能有一个return语句被执行。3.如果没有return语句或return语句后面没有跟表达式,则会自动返回None。5.3.1return语句与函数的返回值【例5-22】定义一个函数,返回若干数中的最大数。>>>defmy_max(*args):#因为是求若干数的最大值,这里使用了不定长参数 max=args[0]#使用擂台算法求出最大值 foreleminargs[1:]: ifmax<elem: max=elem returnmax#返回最大值>>>print(my_max(1,3,5,5,12,14))#调用函数并输出函数的返回值14

5.3.1return语句与函数的返回值

【例5-23】定义一个没有返回值的函数。>>>defhello(): print("HelloWorld")#输出”HelloWorld”

>>>print(hello())#调用函数并输出函数的返回值HelloWorldNone#函数的返回值为None

5.3.2多条return语句

return语句是可选的,函数中允许有多个return语句,但每次调用只会有一个return语句被执行,return语句被执行后,表示函数执行完毕,并将结果返回,哪怕后面仍有代码,也将跳出函数体。【例5-24】求两个数中的最小值。>>>defmy_min(a,b): ifa<b: returna returnb

>>>print(my_min(2,3))2从运行结果可以看出,在执行完returna后函数结束,并没有执行后面的returnb。

5.3.3返回多个值

在python中,一个函数还可以同时返回多个值,但事实上,函数的返回值仍是一个对象,只不过这个对象可以包含更多的元素,如列表、元组、字典对象等。【例5-25】输入圆的半径,求出圆的面积及周长。>>>defcircle(r): area=3.14*r*r perimeter=3.14*2*r return(area,perimeter)#通过元组对象,将圆的面积、周长返回。

>>>circle_area,circle_perimeter=circle(3)#将元组拆包>>>print(circle_area,circle_perimeter)28.25999999999999818.84

5.4变量的作用域

变量的作用域就是变量的可访问空间,即变量起作用的范围,变量第一次出现的位置决定了变量的作用域。python中的作用域一共有4种类型,分别是:L(Local),局部作用域。E(Enclosing),嵌套作用域G(Global),全局作用域B(Built-in),内置作用域python会以L→E→G→B的顺序搜索变量,当局部作用域找不到时,会去嵌套作用域找(如闭包,闭包在后面会有介绍),再找不到,就去全局作用域找,最后再去内置作用域找。python中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,在不同作用域内同名变量之间互不影响。而其它的代码块(如if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这这些语句内定义的变量,外部也可以访问。

5.4变量的作用域

【例5-26】观察搜索规则>>>x=1>>>y=2.5>>>defout_function(): x=3 definner_function(): print(x) print(y) print(int(y)) inner_function()

>>>out_function()32.52说明:1.当在inner_function中不存在x时,他去了相对于它的嵌套作用域找,所以输出的是3而不是1。

2.

y不存在时,先去嵌套作用域找,发现也没有,就去全局作用域找,找到之后输出了2.5。3.最后输出int(y),int函数在程序中并没有定义,但是程序并没有报错,因为在内置作用域中有int函数名。内置作用域主要是一些内置函数名和关键字等。

4.嵌套作用域是相对的,x=3相对于inner_function来说属于嵌套作用域,而对于out_function来说他就在局部作用域了。

5.4.2局部变量与全局变量

1.局部变量在一个函数内部定义的变量是局部变量(localvariables),处于局部作用域,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能访问这些变量的。【例5-27】观察局部变量的作用范围>>>defl_variable(): a=3 print(a)#输出a的值

>>>l_variable()#调用函数3>>>print(a)#直接输出a的值Traceback(mostrecentcalllast):File"<pyshell#5>",line1,in<module>print(a)NameError:name'a'isnotdefined说明:1.定义了一个函数,并在函数内声明了一个变量a,然后输出a的值。在调用函数时,输出了a的值。2.但在函数外部,输出a的值得时候,出现了错误NameError:name'a'isnotdefined,这是因为a变量的作用范围只在函数内部,在函数外部并不存在a变量。

5.4.2局部变量与全局变量

2.全局变量在函数外部定义的变量称为全局变量(globalvariables),处于局部作用域。全局变量可以在整个程序范围内访问。【例5-28】观察全局变量的作用范围>>>my_str="Hello">>>defg_variable1(): print(my_str)

>>>defg_variable2(): print(my_str+"World")

>>>g_variable1()Hello>>>g_variable2()HelloWorld>>>print(my_str)Hello说明:1.在函数外部定义了一个变量my_str,它是一个全局变量,我们在g_variable1和g_variable2函数中都成功访问了这个变量,最后在函数外部也成功访问了这个变量。

5.4.3global和nonlocal关键字

1.global关键字全局变量既然能够在整个程序范围内访问,那么如果在函数内部想改变全局变量的值时应该怎么做呢?【例5-29】不使用关键字global,在函数内部改变全局变量的值。>>>a=1#声明了一个全局变量a>>>defchange_g_variable1(): a=2#在函数内试图改变全局a的值 print(a)#输出a的值>>>change_g_variable1()#调用函数2>>>print(a)#输出a的值1说明:1.在函数内直接用a=2这条语句并没有改变全局变量a的值,而是在change_g_variable1函数中创建了一个局部变量a,这个a引用了int对象2,所以调用函数输出的a的值为2

2.函数外,无法访问函数内的a,访问的是全局变量a,所以输出的是1。

5.4.3global和nonlocal关键字

【例5-30】使用关键字global,在函数内部改变全局变量的值。>>>a=1#声明了一个全局变量a>>>defchange_g_variable2(): globala#使用global关键字声明为全局变量a a=2 print(a)

>>>change_g_variable2()2>>>print(a)2说明:1.通过global关键字声明函数内的a为全局变量a

。2.通过a=2,改变a引用的对象,使a指向了int对象2,全局变量a就被修改了。

5.4.3global和nonlocal关键字

2.nonlocal关键字和global关键字的作用非常相似,只不过nonlocal关键字是用来修改嵌套作用域中的变量的。nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,则会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)。【例5-31】不使用关键字nonlocal,在函数内部改变上一级函数中变量的值。>>>defouter(): num=15 definner():#嵌套定了一个函数inner num=20#在inner函数中改变num的值 print(num) inner()#调用inner函数 print(num)

>>>outer()#调用outer函数2015说明:1.我们在outer函数内部嵌套定义了一个inner函数,outer就是inner函数的上一级函数。outer函数中的num对于inner函数来说,它处于嵌套作用域。2.当在inner函数内执行代码num=20时,并不是将outer函数内的num更改,而是在inner函数内部创建了一个局部变量num并使它指向int对象20,所以第一个输出为20。3.执行完inner函数后,回到了outer函数,输出num,在outer函数内存在局部变量num,所以输出的是15。

5.4.3global和nonlocal关键字

【例5-32】使用关键字nonlocal,在函数内部改变上一级函数中变量的值。>>>defouter(): num=15 definner():#嵌套定了一个函数inner nonlocalnum num=20#在inner函数中改变num的值 print(num) inner()#调用inner函数 print(num)

>>>outer()2020说明:1.nonlocal关键字声明inner函数内的num为上一级函数的局部变量num,再通过num=20,改变num引用的对象,使num指向了int对象20,所以num值就被修改了。

5.4.3global和nonlocal关键字

【例5-33】当上一级函数中不存在nonlocal声明的变量时。>>>defouter(): definner(): nonlocalnum num=20 print(num) inner()#调用inner函数 print(num)

SyntaxError:nobindingfornonlocal'num'found说明:1.当上一级函数中不存在num变量时,在函数定义结束时就会抛出语法错误SyntaxError:nobindingfornonlocal'num'found。

5.5lambada表达式

lambda表达式可以用来声明匿名函数。所谓匿名函数,就是没有名字的、临时使用的小函数。lambda表达式只可以包含一个表达式,所以能够封装的逻辑有限,通常是在需要一个功能简单的函数,同时又不想费神去给这个函数命名时,我们就会使用lambda。需要注意的是,我们使用lambda表达式的目的不是为了替代def关键字定义的函数,而是用在封装简单且非重用的代码上。在python中,lambda的语法是:lambda[形参列表]:表达式其中lambda是python预留的关键字;形参列表为可选项,可以有参数,也可以没有,lambda也支持默认值参数。当有多个参数时,参数之间用逗号分隔;表达式的值将会作为匿名函数的返回值。调用匿名函数时,通常是把匿名函数赋值给一个变量,再利用变量来调用。

5.5lambada表达式

【例5-34】对比def关键字定义函数与lambda表达式定义匿名函数。>>>defmy_add(a,b): returna+b

>>>print(my_add(3,4))7>>>f=lambdaa,b:a+b>>>print(f(3,4))7【例5-35】对比def关键字和lambda处理简单双分支>>>defbig_1(a,b=4): ifa>=b: returna else: returnb>>>print(big_1(3))4>>>big_2=lambdaa,b=4:aifa>=belseb>>>print(big_2(3))4

5.5.2lambda函数特性

1.

lambda表达式定义了一个匿名函数。所谓匿名函数,通俗地说就是没有名字的函数。例如:>>>print(lambdax:x+1)<function<lambda>at0x10d8aa430>直接输出这个表达式,我们可以发现是一个函数的入口地址,因此说lambda函数是匿名函数。2.

lambda函数可以有输入和一定有返回值:输入是传入到参数列表的值,返回值则是返回表达式的值。例如:>>>f=lambdax:x+1>>>print(f(3))4其中传入到参数列表的值是3,返回的是4。5.5.2lambda函数特性3.

lambda函数一般功能简单:单行函数表达式决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。因此,在编写代码时,我们要考虑需要解决的问题的复杂程度和代码的重用,再决定是否要使用lambda函数。例如:>>>defmy_add(n): returnn+n>>>print(list(map(my_add,[1,2,3,4,5])))[2,4,6,8,10]>>>print(list(map(lambdan:n+n,[1,2,3,4,5])))[2,4,6,8,10]上面我们可以看出,用lambda函数要比自定义一个函数简洁方便一些,毕竟不用为了这种使用一次的函数去想一个名字。对于一些简单函数来说,使用lambda表达式比较方便。4.

由于python对lambda表达式的支持有限,适合处理一些简单逻辑,如简单多分支程序if...else(if...else...),对于异常处理程序try...except...的问题时,lambda函数就不适用了。如果一个lambda表达式导致一段代码难以理解时,FredrikLundh给了一个很好的重构方法:先编写一个注释来解释这段代码,再从这段注释中提炼出一个词来概括注释,再将lambda表达式换成def语句,用提炼出的词作为函数名,最后删除注释。

5.6.1闭包

我们先来看看维基百科时怎么定义闭包的。在计算机科学中,闭包(Closure)是词法闭包(LexicalClosure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。通俗的来说,闭包就是一个在函数体中引用不在函数体中定义的非全局变量的函数。

5.6.1闭包

这个概念现在看起来有些晦涩难懂,我们通过一个例子来理解。【例5-36】定义一个函数求一系列数的总和。这一系列数并不是一次给出,而是一个个给出。比如求某个商场一个月的营业额,这个月营业额是当前营业额总数加上当天的营业额,直到加满一个月为止。>>>defmy_sum(): s=0#s为局部变量 defadd(num):#嵌套定义add函数 nonlocals#自由变量 s=s+num returns returnadd#返回add函数

>>>turnover=my_sum()#调用my_sum函数,结果返回add函数的入口地址>>>print(turnover(10))#相当于调用add函数10>>>print(turnover(20))30>>>print(turnover(20))50说明1.在add函数里通过nonlocal关键字引用它嵌套作用域中的变量s,使得add函数不仅可以访问s同时也可以修改s。由于s并不在add函数内定义,所以add函数中的s被称为自由变量(freevariable)。所谓的自由变量指的是未在局部作用域中绑定的变量。add函数中有自由变量s,add函数被称为闭包。2.接着我们再继续看调用函数部分。调用my_sum函数,函数返回add函数的入口地址,所以变量turnover指向add函数,所以语句turnover(10),其实相当于调用add函数。一般来说,随之函数调用的结束,它的局部变量将会被销毁,释放内存。但这次不一样了,当my_sum函数调用结束后,它发现add函数包含了s自由变量的绑定,所以这个变量不被销毁。这也是为什么说自由变量将和闭包一同存在,即使已经离开了创造它的环境也不例外。

5.6.2装饰器

装饰器本质上也是一个函数,它的参数是另一个函数(被装饰函数),返回值也是一个函数。装饰器的主要作用就是在不修改被装饰函数的基础上,对被装饰函数做一些处理或者为它增加额外的功能。【例5-37】定义一个装饰器,用于计算求和函数的运行时间。>>>defdecorate(fun): defrun_time(n): start_time=datetime.datetime.now() s=fun(n)#自由变量fun,调用fun函数 over_time=datetime.datetime.now() use_time=over_time-start_time print("该函数运行时间共%s秒"%use_time) returns#将fun函数的结果返回 returnrun_time#返回run_time函数5.6.2装饰器>>>defmy_sum(n): s=0 foriinrange(n+1): s=s+i returns>>>f=decorate(my_sum)>>>print(f(10000))该函数运行时间共0:00:00.001146秒50005000说明1.我们先来看看decorate函数,它的参数fun也是一个函数,接着在内部嵌套定义了run_time函数。run_time记录了运行函数fun之前的时间和运行完fun函数的时间,用变量start_time和over_time引用,它们的中间就是调用fun函数,并用s变量引用函数的返回值,最后s作为run_time函数的返回值被返回。值得注意的是,run_time中的fun是自由变量,所以run_time是闭包,fun即使离开了decorate函数,它也会和run_time函数一同存在。2.我们再来看看函数调用。语句f=decorate(my_sum)调用了decorate函数,这个函数返回的是run_time函数,装饰器将原本的my_sum函数替换成了另一个函数run_time,只不过run_time函数保留了my_sum函数的功能。3.通过这个装饰器扩展了my_sum函数的功能。在装饰器中,因为要保留原有函数的功能,通常情况下会有调用原函数的代码,所以装饰器中返回函数的参数要包含原函数的参数。

5.6.2装饰器

到这里我们实现了装饰器的功能,但是每次在使用装饰器的时候都要显示的调用它,就会出现像这样的代码f=decorate(my_sum)。当需要装饰的函数较多时,这是一个繁琐的工作,所以python给我们提供了一个语法糖,我们只需要在被装饰函数的上面加上@装饰器名称,就使用了装饰器。【例5-38】使用语法糖装饰my_sum函数。>>>@decoratedefmy_sum(n): s=0 foriinrange(n+1): s=s+i returns>>>my_sum(10000)该函数运行时间共0:00:00.000826秒50005000这个语法糖的效果等价于my_sum=decorate(my_sum),它会在加载模块时立即执行。5.6.2装饰器2.参数化装饰器python把被装饰函数当做第一个参数传递给装饰器函数,那怎么样让装饰器接受其他参数呢?答案是:用一个函数将装饰器包裹起来,这个函数被称为装饰器工厂函数,把参数传给这个装饰器工厂函数,然后返回一个装饰器。【例5-39】在例5-37基础上增加一个参数active,用来启用或禁用计算求和函数运行时间功能。>>>defdecorate_factory(active=True): defdecorate(fun): defrun_time(n): start_time=datetime.datetime.now() s=fun(n) over_time=datetime.datetime.now() use_time=over_time-start_time print("该函数运行时间共%s秒"%use_time) returns ifactive:#当active为真时,返回run_time函数 returnrun_time returnfun#当active为假时,返回fun函数 returndecorate #返回decorate函数5.6.2装饰器>>>@decorate_factory() defmy_sum_1(n): s=0 foriinrange(n+1): s=s+i returns

>>>@decorate_factory(False) defmy_sum_2(n): s=0 foriinrange(n+1): s=s+i returns

>>>print(my_sum_1(10000))该函数运行时间共0:00:00.000916秒50005000>>>print(my_sum_2(10000))50005000说明1.最外层的decorate_factory函数时装饰器工厂函数,它的返回值是一个装饰器即decorate函数。装饰器函数decorate和之前的的装饰器一样返回一个函数,只不过在这里多了一个选择结构,当参数active为真时,返回run_time函数去替换原函数,如果为假则返回原函数,即对原函数没有做任何处理。2.在使用语法糖对my_sum_1函数进行装饰时和之前的不一样,这里使用的是@decorate_factory(),加了括号。这是因为decorate_factory并不是装饰器函数,所以通过加括号,去调用它。调用decorate_factory函数,它会返回装饰器函数decorate,再用decorate装饰my_sum_1函数。由于这里没有给active传参,使用的是默认参数True,所以结果把原函数替换成了run_time函数,调用my_sum_1(10000),会输出运行时间和求和结果。同理@decorate_factory(False)对my_sum_2函数装饰时,先返回decorate函数去装饰my_sum_2,由于active的值为False,最后将原函数返回,所以这里装饰之后的原函数并没有任何改变,调用my_sum_2(10000),只输出了求和结果。5.6.2装饰器3.叠放装饰器在上面的例子当中,不管是无参数装饰器还是参数化装饰器,都只使用了一个装饰器,而python是支持叠放装饰器。叠放三个装饰器的代码如下:@deco_1@deco_2@deco_3deffun_1(): pass上面这段代码是把deco_1、deco_2和deco_3三个装饰器按顺序应用到fun_1函数上,它等价于fun_1=deco_1(deco_2(deco_3(fun_1))),这里不再给出具体实例。5.7递归函数现实生活中的递归现象5.7递归函数一个函数在定义其函数体时直接或间接调用它自身称为递归调用,这种函数被称为递归函数。python允许函数的递归调用,在递归调用中,主调函数同时也是被调函数。执行递归函数将反复调用自身,每次调用一次进入新的一层。递归函数的二个要素1.终止条件(stoppingcondition)和递归方程;2.具备这两个要素,才能在有限次计算后得出结果。5.7递归函数【例5-40】定义一个直接调用和一个间接调用自身的递归函数。#直接调用自身的递归函数>>>defdirect_fun(): print("direct") direct_fun()#调用自身#间接调用自身的递归函数>>>defindirect_fun_1(): print("fromindirect_fun_1") indirect_fun_2()#调用indirect_fun_2函数

>>>defindirect_fun_2(): print("fromindirect_fun_2") indirect_fun_1()#调用indirect_fun_1函数说明1.直接调用自身的递归函数,就是在函数体内部调用自己。2.间接调用的递归函数则是通过多个函数相互调用。3.如果试着运行了上面的代码,就会发现这两个递归函数都陷入了无休止的调用自身,直到出现了RecursionError:maximumrecursiondepthexceededwhilecallingapythonobject错误。因为默认情况下,函数的调用深度最大值为1000,超过1000时就会出现这个错误。5.7递归函数

说明1.我们定义了一个函数fac,在函数内就调用了本函数,这是递归函数的典型特征,一个求n的阶乘的问题变成了求n-1的阶乘的问题。这两个问题本质上是一样的只不过问题规模变小了。2.随着不断的递归调用,问题的规模也不断缩小,直到当n等于1或0时,不再递归递调用而是直接返回1,这样防止了陷入死循环。5.7递归函数执行过程fact(n=3):return

3*fact(2)fact(n=2):return

2*fact(1)fact(n=1):return

15.7递归函数一位法国数学家曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?5.7递归函数例【例5-42】汉诺塔问题。第1个老和尚这样做:命令第2个人将63个盘子从A柱移到B柱;自己将1个盘子(最底下的、最大的盘子)从A柱移到C柱;再命令第2个人将63个盘子从B柱移到C柱;586463……..21ABC63……..216463……..21问题:将A柱上的64个盘子从A柱搬到C柱;规则:1.每次只能搬一个盘子;2.任何柱子上的盘子

必须保证小盘在上,大盘在下;世界就将在一声霹雳中消灭5.7递归函数终止条件n==1时,将1个盘子从A柱移到C柱递归方程n>1时,将第n个盘子从A柱移到C柱的步骤分解为三步;将n-1个盘子从A柱移到B柱;将1个盘子(最底下的、最大的盘子)从A柱移到C柱;将n-1个盘子从B柱移到C柱;>>>defhanoi(n,a,b,c): ifn==1: print("{}---->{}".format(a,c)) else: hanoi(n-1,a,c,b) print("{}---->{}".format(a,c)) hanoi(n-1,b,a,c)>>>hanoi(3,'a','b','c')a---->ca---->bc---->ba---->cb---

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论