一、概述
Python的程序由包、模块和函数组成
。本章主要介绍函数和模块。函数是一段可重用的有名称的代码
。使用函数可以减少代码重复性,增加程序的模块化。通过输入的参数值,返回需要的结果,并可存储在文件中供以后使用。几乎任何Python代码都可放在函数中。Python为函数提供了强大支持。模块是处理某一类问题的集合,模块由函数和类组成
。模块和常规Python程序之间的唯一区别是用途不同:模块用于编写其他程序。因此,模块通常没有所谓的main函数,即可执行语句。包是一个完成特定任务的工具箱
,Python提供了许多有用的工具包,如字符串处理、图形用户接口、Web应用、图像处理等。使用自带的工具包,可以提高程序开发效率、减少编程复杂度,达到代码重用的效果。- 函数、模块及包均可以自定义。
Python的程序结构:
说明:
- Python自带的工具包和模块安装在其安装目录的Lib子目录中。
- 例如:Lib目录中的xml文件夹。xml文件夹就是一个包,该包用于完成XML的应用开发,xml包中包含四个子包:dom、sax、etree和parsers。文件
__init__.py
是xml包的注册文件,若无此文件,Python将不能识别xml包。 - 注意:包必须至少含有一个
__init__.py
文件。__init__.py
文件的内容可以为空,它用于标识当前文件夹是一个包。
二、函数
1、函数的定义及调用
- 函数定义格式:
def 函数名([形参表]):
函数体语句序列
[return 表达式] #可选项,即有的函数可以没有返回值。
- 函数调用格式:
函数名([实参表])
- 说明:
- 函数必须先定义,后使用;
- 函数名与变量名的命名规则相同,只能包含字母、数字和下划线_,且不能以数字打头。
例5-1:定义计算圆面积的函数
2、函数的参数
- 在C、C++中,参数的传递有值传递和引用传递两种方式。Python中任何东西都是对象,所以参数只支持引用传递的方式。
- Python通过名称绑定的机制,把实际参数的值和形式参数的名称绑定在一起,即把形式参数传递到函数所在的局部命名空间中,形式参数和实际参数指向内存中同一个存储空间。
(1)按引用传递参数
- 向函数传递参数时,Python采用按引用传递的方式。
- 这意味着当传递参数时,函数将使用新变量名来引用原始值。
- 这种方式形参按位置引用实参值,也称位置参数。
例5-2:求任意两个数的和
内存状态:
-
将x和y分别设置为3和4的内存状态:
-
刚调用add(x,y)后的内存状态,a和b分别指向x和y指向的值。
(2)默认值参数
- 函数的参数支持默认值。当某个参数没有传递实际的值时,函数将使用默认参数计算。
- 带默认值的参数不能位于没有默认值的参数前面。
例5-3:默认值参数示例
(3)关键字参数
- 关键字参数有两大好处:
- 清晰地指出了参数值,有助于提高程序的可读性;
- 关键字参数的顺序无关紧要。
- 调用使用关键字参数的函数时,以
param=value
的方式传递参数
例5-4:关键字参数示例
(4)不定长参数
- 不定长参数,即在调用函数时可以接收任意数量的实参,这些实参在传递给函数时会被封装成元组(位置参数)或字典(关键字参数)形式。
- 在参数名前面加一个“*”,表示参数是以形参名为标识符的元组。
- 在参数名前加两个“*”,表示参数是以形参名为标识符的字典,其中关键字为“键”,参数值为“值”。
参数传递顺序:
- 在定义函数时,可以混合使用多种参数传递方式,此时要遵循以下规则:
- 关键字参数应放在位置参数后面
- 元组参数必须在关键字参数后面
- 字典参数要放在元组参数后面
- 在调用函数时,首先按位置顺序传递参数,其次按关键字传递参数。多余的非关键字参数传递给元组,多余的关键字参数传递给字典。
- 对于使用位置参数形式的不定长参数,Python也允许普通形参放在不定长参数后面,但此时要求在调用函数时必须使用关键字参数方式给不定长参数后面的形参传递实参。
带不定长参数的函数定义格式:
def 函数名([普通形参列表,] *不定长参数名 [,普通形参列表]):
函数体
或:
def 函数名([普通形参列表,] **不定长参数名):
函数体
例5-5:不定长参数示例
(5)拆分参数列表
- 如果一个函数所需要的参数已经存储在了列表、元组或字典中,则可以直接从列表、元组或字典中拆分出来函数所需要的这些参数。
- 其中列表、元组拆分出来的结果作为位置参数,而字典拆分出来的结果作为关键字参数。
例5-6:不通过拆分方法传递参数
例5-7:通过拆分方法传递参数
说明:
- 实参
*ls
的作用是把列表ls中的所有元素拆分出来作为SumVal的实参,即等价于SumVal(3, 5, 2, 7, 1)
例5-8:字典拆分结果作为函数关键字参数
说明:
- 实参
**d
的作用是把字典d中的所有元素拆分出来作为StudentInfo的实参,即等价于StudentInfo(country= '中国',chineselevel= '优秀', name='李明')
3、函数的调用
函数调用执行的四个步骤:
- 调用程序在调用处暂停执行
- 函数的形参在调用时被赋值为实参
- 执行函数体
- 函数被调用结束,给出返回值
例5-9:生日歌程序
调用过程:
4、函数的返回值
- return语句:程序退出该函数,并返回到函数被调用的地方(一般来说,若函数有返回值,则返回至函数调用时的那条语句参与运算,否则返回至函数调用语句的下一条继续向后执行)
- return语句返回的值传递给调用程序 ,Python函数的返回值的形式:
- 没有返回值
- 返回一个值
- 返回多个值
(1)无返回值的return语句等价于return None
等价于:
(2)返回值可以是一个变量,也可以是一个表达式
等价于:
(3)使用return语句返回多个值
例5-10:对两个数做加法和减法,并返回计算结果。
5、函数的嵌套
- C、C++都支持函数的嵌套调用,Python不仅支持函数的嵌套调用,还支持函数的嵌套定义,即在函数内部再定义函数。
- 建议:尽量不要在函数内部定义函数,这种方式不便于程序维护,容易造成逻辑上的混乱,且嵌套定义的函数层次越多,程序维护的代价就越大。
例5-11:函数嵌套应用
- 计算表达式(x+y)*(m-n)的值
- 分别使用函数的嵌套调用、函数的嵌套定义以及函数嵌套定义时直接引用外部函数的变量等三种方式
嵌套调用函数:
嵌套定义函数:
函数嵌套定义,内部函数直接引用外部函数的变量:
6、变量的作用域
变量的作用域是指变量的作用范围,即定义一个变量后,在哪些地方可以使用这个变量。
按照作用域的不同,Python中的变量可分为局部变量和全局变量。
(1)局部变量
- 局部变量是只能在函数内部使用的变量。
- 函数一旦结束,局部变量的生命周期也就结束。
- 局部变量的作用范围只在其被创建的函数内有效,函数形参也属于局部变量。
例5-12:局部变量应用
(2)全局变量
- 在函数之外定义的变量都可以称为全局变量;
- 全局变量是能够被不同的函数、类或文件共享的变量;
- 全局变量可以被文件内部的任何函数和外部文件访问;
- 全局变量通常在文件的开始处定义;
- 若要在函数内部修改全局变量的值,必须用global语句声明。
例如:同名变量
• 函数体中新创建一个与全局变量同名的变量,则全局变量被暂时隐藏
• 函数体中直接引用全局变量,未对全局变量进行修改,则函数体中的变量即全局变量
例如:同名变量(看上去是函数体中全局变量count被修改,实际上系统认为count是局部变量)
报错说明:
- 该错误是由于python在函数中发现对COUNT变量进行了赋值,会将其加入到函数的局部命名空间(实际上,这是在函数执行到赋值操作之前发生的)。
- 进行赋值操作时。赋值操作符的右边引用了COUNT变量。而这时COUNT变量仅仅是被加入到了函数的局部命名空间,而没有被赋值,所以会发生上述错误。实际上。这里问题就出在赋值操作的地方,由于有赋值操作导致该变量被加入到了函数的局部命名空间。
- 假设没有赋值,仅仅是引用该变量,是没有问题的,如上例所示。
改变全局变量的值:
- 若希望在函数中修改全局变量的值,就要在函数中对该变量进行global声明,以告诉python解释器,该变量是全局命名空间中的,例如:
例5-13:全局变量应用
运行结果:
全局变量使用注意事项:
(1)统一管理全局变量
- 可以将全局变量放到一个专门的文件中,便于统一管理。
- gl.py
例5-14:
(2)慎用全局变量
- 应该尽量避免使用全局变量。因为不同的模块都可以自由地访问全局变量,可能会导致全局变量的不可预知性。
- 对于上例中gl.py中的全局变量,若程序员甲修改了_a的值,程序员乙同时也要使用_a,此时就可能导致程序的错误。这种错误是很难发现和更正的。
- 全局变量降低了函数或模块之间的通用性,不同的函数或模块都要依赖于全局变量。同样,全局变量降低了代码的可读性,阅读程序者并不知道调用的某个变量是全局变量。
课堂练习一:
7、递归函数
(1)递归的概念
- 递归函数可以在函数主体内直接或间接地调用自己,即函数的嵌套是函数本身。
- 在进行问题分解时,若发现分解之后待解决的子问题与原问题有着相同的特性和解法,只是在问题规模上与原问题相比有所减小,此时,就可以设计递归函数进行求解。
- 递归是一种程序设计方法,使用递归可以减少重复的代码,使程序变得简洁。
- 递归的过程分为两个阶段:递推和回归。
递归函数的原理:
- 第一阶段,递归函数在内部调用自己。每一次函数调用又重新开始执行此函数的代码,直到某一级递归程序结束。
- 第二阶段,递归函数从后往前返回。递归函数从最后一级开始返回,一直返回到第一次调用的函数体内。即递归函数逐级调用完毕后,再按相反的顺序逐级返回。
(2)递归的实现
- 一个问题能否用递归实现,看其是否具有下面的特点:
- 需有完成任务的递推公式。
- 结束递归的条件。
- 编写递归函数时,程序中必须有相应的语句:
- 一个递归调用语句。
- 测试结束语句。先测试,后递归调用。
例5-15:用递归方法求n!
递推公式:
例如,使用递归计算5!的过程:
源程序及执行结果:
例5-16:编程求出Fibonacci数列的第n项
-
Fibonacci数列定义如下:
-
假定求出第八项。
-
分析:Fibonacci数列的计算具备递归的条件。首先有递推公式F(n)=F(n-1)+F(n-2),第二有结束递归的条件即n=1或n=2时不再递归。
源程序及执行结果:
(3)递归的评价与消除递归
- 递归程序虽然易读、易编,但需要占用额外的内存空间,并且执行速度也受影响。当问题规模较大时,递归调用会涉及到很多层的函数调用,一方面会由于栈操作影响程序运行速度,另一方面在
Python中有栈的限制、太多层的函数调用会引起栈溢出问题。
- 是否利用递归编程要看实际问题,如果要节约内存就用循环语句实现。若对内存要求并不高,可以用递归编程。
- 如果不用递归程序很难实现,则只能选择递归算法。
8、lambda函数
- lambda函数用于创建一个匿名函数,函数名未和标识符进行绑定。
- lambda函数是一种不使用def定义函数的形式,其作用是能快速定义一个简短的函数。
- lambda函数的函数体只是一个表达式, 所以lambda函数通常只能实现比较简单的功能,返回一些简单的运算结果。
lambda函数格式:
- 格式:
lambda 参数1,参数2,…,参数n:表达式
- 功能:
- 可以将lambda直接作为函数使用。
- 也可以将lambda赋值给一个变量,变量即可作为函数使用
- 其中:
- 参数1—参数n:相当于普通函数的参数;表达式:相当于普通函数的返回值。
- 说明:
- lambda函数用于定义简单的、能够在一行内表示的函数,返回一个函数类型。
- lambda函数可用于需要函数对象的场景。
例如:
Lambda函数执行过程示例:
例5-17:用lambda函数改造例5-11-3。
执行结果
例5-18:使用lambda定义求绝对值的匿名函数
例如:用lambda函数作为max函数的排序规则
9、生成器(Generator)函数
- 生成器(generator)是用来创建Python序列的一个对象,可将其看作是一个不断产生值的函数;
- 使用它可以迭代庞大的序列,且不需要在内存中创建和存储整个序列;
- 生成器函数的定义与普通函数相同,只是将return换成了yield ;
- 生成器每次产生一个值(yield语句),函数被冻结,被唤醒后再产生一个值;
- 如果生成的序列比较简单,可以使用生成器推导式(后续介绍)。
生成器函数格式:
- 格式:
def 函数名(参数列表):
…
yield 表达式
- 说明:
- Generator函数的定义与普通函数的区别只是在函数体内使用yield生成数据项。
- Generator函数可以被for循环遍历,且可以通过__next__()方法(Python 2是next方法)获得yield生成的数据项。
yeild与return的区别:
- yield语句
- 立即返回一个值
- 下一次迭代生成器函数时,从yield语句后的语句继续执行,直到再次执行yield语句返回一个值,或终止
- return语句
- 终止函数的执行,下次调用会重新执行函数
例5-19:用三种方法求斐波那契(Fibonacci)数列的前N项
方法一:输出斐波那契数列前 N 项
说明:
- 结果没有问题,但直接在 fab 函数中用 print 输出数字会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的序列。
- 要提高 fab 函数的可复用性,最好不要直接输出结果,而是返回一个 list(Python中的数据结构之一,是序列之一,由一组元素组成,方括号定界,值可改变。后续详细介绍)
方法二:定义一个函数,返回一个列表,列表中包含了斐波那契数列前 N 项
方法三:使用yield
说明:
- 方法三与方法一相比,仅仅把 print b 改为了 yield b,就在保持简洁性的同时获得了 iterable 的效果。
- 也可以显式地调用 fab(5) 的
__next__()
方法,这样可以更清楚地看到 fab 的执行流程。 - 当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。
生成器的优势:
生成器相比一次列出所有内容的优势:
- 更节省存储空间
- 响应更迅速
- 使用更灵活
10、高阶函数
- 高阶函数是指把函数作为参数的一种函数。
- 函数不仅可以赋给形参,也可以赋给普通变量。赋值后,即可以用变量名替代函数名完成函数调用。
例5-20:高阶函数应用
11、猴子补丁
- 猴子补丁是指在运行时动态替换已有的代码。主要用于在不修改已有代码情况下修改其功能或增加新功能的支持。
- 例如,在使用第三方模块时,模块中的某些方法可能无法满足需求。此时,可以在不修改这些方法代码的情况下,通过猴子补丁用自己编写的新方法进行替代,从而实现一些新的功能。
例5-21:猴子补丁应用
三、模块
- 模块实际上是将一组函数放在一起共享公共的主题;
- 将这些函数存储于一个.py文件中;
- 使用import命令导入。
1、模块的创建及导入
- 创建模块,即创建一个.py文件,在其中包含用于完成任务的变量、类和函数,不包括所谓的main函数,即调用该模块中函数的语句。
- 模块使用之前要导入该模块,导入方法之前已做过介绍。
例5-22:创建一个在屏幕上输出各种图形的模块
- 创建模块,用于在屏幕上显示:长方形、正方形和三角形;
图形用“*”组成。 - 定义的模块shapes及使用模块的源程序:
执行结果:
例5-23:创建一个求圆面积、圆周长、圆表面积和圆体积的模块
调用方式一:
调用方式二:
2、模块的属性
-
模块有一些内置属性,用于完成特定的任务。
-
例5-17中的dir(shapes),就输出了模块shapes的属性。
- 如:
__doc__
:模块中用于描述的文档字符串__name__
:模块名__file__
:模块保存的路径
关于内置属性__name__
:
- 每个模块中都有的一个内置属性
__name__
。 __name__
的作用是获取当前模块的名称,如果当前模块是单独执行的,则其__name__
的值就是__main__
;否则,如果是作为模块导入,则其__name__
的值就是模块的名字。
例如:__name__
属性的用法
例如:修改例5-1定义求圆面积的函数程序
单独执行circlearea.py
circlearea.py作为模块被例5-1_test.py调用
3、内置模块__builtins__的常用函数
- Python提供了一个内置模块
__builtins__
,无需导入。 - 该模块定义了一些常用函数,利用这些函数可以实现数据类型的转换、数据的计算、序列的处理等功能。
- 可通过
dir(__builtins__)
查看所包含内容。
内置模块常用函数(一)
内置模块常用函数(二)
(1)filter()
- 声明:
class filter(object)
filter(function or None, iterable)-->filter object
- 功能:filter()可以对某个序列做过滤处理,根据自定义函数返回的结果是否为真来过滤,并一次性返回处理结果。返回结果是filter对象。
例5-24:filter()函数应用
执行结果
(2)reduce()
- 声明:
reduce(func,squence[,initial])->value
- 功能:对序列中的元素进行连续操作。例如:可对某个序列中的元素进行累加、累乘和阶乘等操作。若提供初始值,则将其置于所有序列项之前,当序列为空时,则作为缺省值。
- 说明:在Python 2中,reduce存在于内置模块中,可直接调用。而在Python 3中将其移到了functools模块中,所以使用之前要先引入。
例5-25:reduce()函数应用
(3)map()
- 声明:
class map(object)
map(func, iterables)-->map object
- 功能:对多个序列的每个元素都执行相同的操作,并返回一个map对象。
例5-26:map()函数应用—求列表中数字的幂运算。
执行结果
例5-27:map()函数应用—输入一个列表,将其反转后输出新的列表。
四、包
- Python中的包(Package)的作用与操作系统中文件夹的作用相似,利用包可以将多个关系密切的模块组成在一起,方便进行程序的重用,也可以有效避免模块命名冲突问题。
- 一个包,就是创建一个文件夹并在该文件定义夹下创建一个
__init__.py
文件(内容可以为空),文件夹的名字就是包名。当包被调用时(使用import也可导入包),则自动执行__init__.py
文件。 - 另外,可以根据需要在该文件夹下再创建子文件夹,子文件夹中创建一个
__init__.py
文件,则又形成了一个子包。 - 模块可以放在任何一个包或子包中,在导入模块时需要指定所在的包和子包的名字。例如,如果要导入包A中的模块B,则需要使用
“import A.B”
。
例5-28:包与模块的关系
- 定义一个包parent。在parent包中创建两个子包pack1和pack2;
- 包pack1中定义了一个模块myModule1,包pack2中定义了一个模块myModule2;
- 最后在包parent中定义了一个模块main,调用包pack1和pack2。
包pack1的初始化程序及myModule1模块:
包pack2的初始化程序及myModule2模块:
包parent中的main模块及执行结果:
五、第三方库(模块)的安装
- Python库的自定义安装
- 找到库所在的网站,根据提示下载安装
- Python库的工具安装,使用pip工具
- 通过pip在线安装库函数,需要联网
- Python库的文件安装
- 下载安装包,通过.whl文件直接安装
(1)自定义安装
- 自定义安装指按照第三方库提供的步骤和方式安装。第三方库都有主页用于维护库的代码和文档。
- 例如:科学计算库numpy,其主页为:http://www.numpy.org/,浏览该网页找到下载链接: https://www.scipy.org/scipylib/download.html
- 根据提示步骤进行安装。
- 自定义安装一般适合于pip中尚无登记或安装失败的第三方库。
(2)库的工具安装:pip
- PyPI(Python Package Index)是python官方的第三方库的仓库,所有人都可以下载第三方库或上传自己开发的库到PyPI。PyPI推荐使用pip包管理器来下载第三方库。
- pip可正常工作在Windows、Mac OS、Unix/Linux等操作系统上,python 2.7.9 和3.4以后的版本已经内置了pip程序,所以不需要安装。
- pip最新的版本(1.5以上的版本),出于安全的考虑,不允许安装非PyPI的URL(Uniform Resource Locator )。
PyPI(Python Package Index)
pip工具的使用帮助:
注意:通过cmd命令进入命令行方式
pip的主要子命令
例如:查看install命令的使用帮助
pip的install命令:
- 命令格式:
pip install [安装库名称]
例如:安装pyinstaller库。该库是一个将Python源程序(.py文件)打包成可执行文件(.exe文件)的第三方库。
安装pyinstaller库
已安装库的版本更新:
命令格式:
pip install -U[安装库名称]
例如:对已安装的numpy库进行版本更新
例5-29:pyinstaller应用
将hello.py文件打包为hello.exe可执行文件。
pyinstaller常用参数
步骤1:在c盘创建工作目录testpy2exe,在该目录下创建hello.py文件
步骤2:以管理员身份进入命令行方式(cmd),执行pyinstaller命令
……
执行后在源文件所在目录下生成两个目录:
dist目录和build目录。其中,build目录是pyinstaller存放临时文件的目录,可以安全删除;最终的打包文件在dist目录中。
步骤3:进入dist子目录,执行hello.exe文件
pip的uninstall命令:
命令格式:
pip uninstall [安装库名称]
例如:卸载已安装的pyinstaller库
pip的list命令:
显示已安装的库:
pip list
显示有更新的库
pip list –outdated
说明:Python安装文件一般用wheel格式—.whl
pip的show命令:
显示一个已安装库的具体信息
pip show [安装库名称]
例如:显示pyinstaller具体信息
pip的search命令:
在PyPI搜索库名或关键字
pip search [关键字]
例如:搜索关键字pyinstaller对应信息。
(3)Python库的文件安装
- 有时通过pip下载文件后无法在Windows下成功安装,为了解决这类问题,美国加州大学尔湾分校提供了一个页面,帮助Python用户获得Windows可直接安装的第三方库文件,链接地址为:(Unofficial Windows Binaries for Python Extension Packages):
- Python库文件的格式
- Python安装文件一般用wheel格式,.whl文件,一种打包文件
例如:安装pandas库
下载:pandas-0.18.1-cp34-cp34m-win_amd64.whl
安装方式选择
- 优先级1:pip工具安装(绝大多数都能成功安装)
- 少部分不成功
- 优先级2:文件安装
- 优先级3:自定义安装
安装第三方库注意事项
- 以管理员身份进入cmd方式;
- 若系统中已安装了多个版本的Python,要在指定版本中安装第三方库,则要进入该版本的Scripts目录下;若使用.whl文件安装,则要将该安装文件也拷贝到该目录下;
- 如果使用PyCharm,则有更为便捷的安装方式。
六、综合举例
1、用函数完善计算—编写求任意数的任意整数次根的函数
进一步完善:将求负数的奇数次根的情况考虑在内:
进一步完善:将分数的情况也考虑在内:
进一步完善:增加一个说明
2、递归经典程序—汉诺(Hanoi)塔问题
- 这是一个古典的数学问题,是一个只能用递归方法解决的问题。
- 问题源于印度一个古老传说,古代有一个梵塔,塔内有三个柱子A、B、C,开始时A柱上有64个盘子,盘子大小不等,大的在下,小的在上。想将这64个盘子从A柱移到C柱,但每次只允许移动一个盘子,且在移动过程中都始终保持大盘在下,小盘在上。在移动过程中可以利用B柱。
递归算法描述:
1、将A上n-1个盘借助C座先移到B座。
2、将A座上剩下的一个盘移到C座。
3、将B上n-1个盘借助于A座移到C座。
说明:
- 上述算法中的第1步和第3步,都是将n-1个盘子从一个座移到另一个座,只是座的名称不同而已。为使之一般化,可将第1步和第3步表示为:
- 将“one”座上n-1个盘移到“two”座(借助“three”座)。只是在第1步和第3步中,one、two、three和A、B、C的对应关系不同。
- 对第1步:one-A、two-B、three-C
- 对第3步:one-B、two-C、three-A
程序及执行结果:
整理不易🚀🚀,关注和收藏后拿走📌📌欢迎留言🧐👋📣✨
欢迎专注我的公众号:AdaCoding 和 Github:AdaCoding123
在这里插入代码片