第5章 函数和代码
一、函数的基本使用
函数是一段具有特定功能的、可重用的语句组,通过函数名来表示和调用。
函数的使用包括两部分:函数的定义和函数的使用
1、函数的定义
Python语言通过保留字def定义函数,语法形式如下:
def <函数名>(<参数列表>):
<函数体>
return <返回值列表>
函数名可以是任何有效的Python标识符,参数列表是调用该函数时传递给它的值,可以有零个、一个或多个,当传递多个参数由逗号分隔,当没有参数时也要保留圆括号。参数列表中的参数是形式参数,简称“形参” ,相当与实际参数的一种符号表示或符号占位符。
函数体是函数每次被调用时被执行的代码,由一行或多行语句组成。如果需要返回值,使用保留字return和返回值列表。函数可以没有return语句,函数体结束后会将控制权返回给调用者。
例如,定义一个整数n的阶乘计算函数,代码如下:
def fact(n):
s=1
for i in range(1,n+1):
s*=i
return s
提示:无return函数
当函数没有return时,仅表示执行一段代码功能。
2、函数的使用
函数的定义也叫函数“声明”,定义后的函数不能直接运行,需要经过“调用”才能得到运行。
调用函数的基本方法如下:
<函数名>(<实际赋值参数列表>)
例如。定义一个整数n的阶乘计算函数,同时对其进行调用,代码如下。
#定义一个对整数n求阶乘的函数
def fact(n):
s=1
for i in range(1,n+1):
s*=i
return s
#调用整数阶乘函数
print(fact(100))
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
每次调用函数可以提供不同参数作为输入,以实现对不同数据的处理;
函数执行后,可以反馈相应的处理结果。函数的执行与黑盒类似,使用者不需要了解函数内部实现原理,只要了解函数的输入输出方式即可。
具体来说,函数的使用一共分为4个步骤。
(1)函数定义
使用def保留字将一段代码定义为函数,需要确定函数名、参数名、参数的个数,使用参数名称作为形式参数(占位符)编写函数内部的功能代码。
(2)函数调用
通过函数名调用函数功能,对函数的各个参数赋予实际值,实际值可以是实际数据,也可以是在调用函数前已经定义过的变量。
(3)函数执行
函数被调用后,使用实际参数(赋予形式参数的实际值)参与函数内部代码的运行,如果有结果则进行输出。
(4)函数返回
函数执行结束后,根据return保留字的指示决定是否返回结果,如果返回结果,则结果将被放置到函数被调用的位置,函数使用完毕,程序继续运行。
编程中大量使用函数已经成为一种编程范式,叫作函数式编程。
在Python中,函数也是有类型的,可以通过type()获得函数类型。函数采用其定义的名字表达,具体为function类型,这是一种Python的内置类型。然而,如果调用函数,则类型为返回值的类型。
def f(x):
return x+1
type(f)
<class 'function'>
type(f(1))
<class 'int'>
上述代码,函数f()是function类型,但其调用f(1)的类型是被调用函数返回值的类型,因为
f(1)返回数字为2,为整数类型,所以f(1)的类型是整数。
Python语言最小函数可以不表达任何功能,如下:
def f():
pass
其中,保留字pass表示不进行任何操作,起到占位作用,因为函数体内部总要编写一行代码,pass用来表示这种占位。对f()的调用不实现任何功能。
二、函数的参数传递
1、可选参数传递
函数的参数在定义时可以指定默认值,当函数被调用时,如果没有传入对应的参数值,则使用函数定义时的默认值替代。带有可选参数的函数定义语法形式如下:
def <函数名>(<非可选参数列表>,<可选参数>=<默认值>):
<函数体>
return <返回值列表>
以两个数的乘法计算函数为例,设y为可选参数,示例如下。
def multiply(x,y=10):
print(x*y)
multiply(99)
990
multiply(99,2)
198
需要注意,可选参数一般放置在非可选参数的后面,即定义函数时,先给出所有非可选参数,然后再分别列出每个可选参数及对应的默认值。
2、参数名称传递
函数调用时,默认采用按照位置顺序的方式传递给函数,例如multiply(99,2)中第一个实参默认赋值给形参x,第二个实参赋值给形参y。
Python语言同时支持函数按照参数名称方式传递参数,语法形式如下:
<函数名>(<参数名>=<实际值>)
以两个数的乘法计算函数为例,观察multiply()函数的调用方式,示例如下。
def multiply(x,y=10):
print(x*y)
multiply(x=99)
990
multiply(y=2,x=99)
198
采用参数名称传递方式不需要保持参数传递的顺序,参数之间的顺序可以任意调整,只需要对每个必要参数赋予实际值即可,这种方式会显著增强程序的可读性。
3、函数的返回值
return语句用来结束函数并将程序返回到函数被调用的位置继续执行。return语句可以出现在函数中任何部分,同时可以将0个、1个或多个函数运算的结果返回给函数被调用处的变量。
以两个数的乘法计算函数为例,观察multiply()函数的返回值,示例如下。
def multiply(x,y=10):
return x*y
s=multiply(99,2)
print(s)
198
提示:多个返回值
当return返回多个值时,这些值形成了一个元组数据类型,由小括号和逗号分隔,例如(a,b,c)。
元组是Python内置的一种组合数据类型。
以两个数的乘法计算函数为例,观察multiply()函数返回多个值的情况,示例如下。
def multiply(x,y=10):
return x*y,x+y
s=multiply(99,2)
print(s)
(198, 101)
a,b=multiply(99,2)
print(a)
198
print(b)
101
当函数存在多种结束条件时,将使用多个return语句,例如:
def manyret(x):
try:
if x>0:
return x+1
else:
return x-1
except:
return 0
在manyret()函数中由于存在try-except异常判断及if-else分支语句,该函数有3个出口,每个出口都有return语句,因此共有3个返回return语句。
三、变量的作用域
根据程序中变量所在的位置和作用范围,变量分为局部变量和全局变量。局部变量仅在函数内部使用,且作用域也在函数内部。全局变量可以跨越函数使用,作用域覆盖整个程序。
1、局部变量
局部变量是指函数内部定义并使用的变量,仅在函数内部有效,当函数退出时变量将不再存在。
以两个数的乘法计算函数为例,multiply()函数内部定义的变量z是局部变量,代码如下:
def multiply(x,y=10):
z=x*y #z是函数内部的局部变量
return z
s=multiply(99,2)
print(s)
198
print(z)
Traceback (most recent call last):
File "<pyshell#45>", line 1, in <module>
print(z)
NameError: name 'z' is not defined
当multiply()函数调用后,变量z将不存在,因此再使用这个变量会报错。
2、全局变量
全局变量指在函数之外定义的变量,在程序执行全过程有效。全局变量在函数内部使用时,需要提前使用保留字global声明,语法形式如下:
global<全局变量>
以两个数的乘法计算函数为例,观察multiply()函数中变量n及对变量n的声明,代码如下。
n=2 #n是全局变量
def multiply(x,y=10):
global n
return x*y*n #使用全局变量n
s=multiply(99,2)
print(s)
396
提示:全局变量声明
使用global对全局变量进行声明时,该变量要与外部全局变量同名。
上例中,变量n是全局变量,在函数multiply()中使用时需要在函数内部使用global声明,声明后即可使用。如果未使用保留字global声明,即使名称相同,也不是全局变量,对比代码如下。
n=2 #n是全局变量
def multiply(x,y=10):
n=x*y
return n #此处的n不是全局变量
s=multiply(99,2)
print(s)
198
print(n)
2
尽管multiply()中使用了变量n,但由于没有用global保留字声明,该变量仍然是局部变量。使用global保留字声明后,外部定义变量的作用域才能进入函数内部,这种声明不能省略。
四、代码复用
函数是程序的一种基本抽象方式,它将一系列代码组织起来通过命名供其他程序使用。函数封装的直接好处是代码复用,任何其他代码只要输入参数即可调用函数,从而避免相同功能代码在调用处重复编写。代码复用有另一个好处,当更新函数功能时,所有被调用处的功能都被更新。
程序由一系列代码组成,如果代码是顺序但无组织,不仅不利于阅读和理解,也很难进行升级和维护。当程序长度在百行以上,如果不划分模块,程序的可读性就已经很糟糕了。解决这一问题最好的方法是将一个程序分割成短小的程序段,每一段程序完成一个小的且特定的功能。利用函数将程序合理划分为多个功能模块,并基于模块设计程序是一种常用的程序设计方法,被称为“模块化设计”。
模块化设计指通过函数的封装功能将程序划分成主程序、子程序和子程序间关系的表达。模块化设计是使用函数设计程序的思考方法,以功能块为基本单位,一般有两个基本要求:
·紧耦合:尽可能合理划分功能块,功能块内部耦合紧密
·松耦合:模块间关系尽可能简单,功能块之间耦合度低
耦合性指程序结构中各模块之间相互关联的程度,它取决于各模块间接口的复杂程度和调用方式。耦合性是影响软件复杂程度和设计质量的一个重要因素。紧耦合指模块或系统间关系紧密,存在较多或复杂的相互调用。紧耦合的缺点在于更新一个模块可能导致其他模块变化,复用较困难。松耦合一般基于消息或协议实现,系统间交互简单。
提示:模块化
松耦合代表了模块化,从系统观念来看,松耦合是总体设计原则。
利用函数只是模块化设计的必要非充分条件,根据计算需求合理划分函数十分重要。一般来说,完成特定功能或经常复用的一组语句应该采用函数来封装,并尽可能减少函数间参数和返回值的数量。
五、函数递归
1、递归定义
函数是一种代码封装,能够被其他程序调用,当然,也可以被函数自身的内部代码调用。这种函数定义中调用函数自身的方式称为递归。
数学上一个经典的递归例子叫阶乘,其中,n的阶乘与n-1的阶乘有关,体现了递归关系。递归不是循环,因为每次递归都会计算比它更小数的阶乘,直到0!。0!是已知的值,被称为递归的基例。当递归到底了,就需要一个能直接算出值的表达式。
递归的两个关键特征
(1)递归基例:存在一个或多个基例,不需要再次递归,它是确定的表达式。
(2)递归链条:所有递归都有一个链条,表现为函数功能的不同值调用。
2、递归构建
以阶乘计算为例,可以把阶乘定义为一个单独的函数fact(n),表示n!.如下代码给出阶乘计算的代码实现。
def fact(n):
if n==0:
return 1
else:
return n*fact(n-1)
num=eval(input("请输入一个整数:"))
print(fact(abs(int(num))))
如第五行所示,fact()函数在其定义内部引用了自身,这就形成了递归过程。fact()函数通过if语句给出了n为0时的基例,当n==0,fact()函数不再递归,返回数值为1,如果n!=0,则通过递归返回n与n-1阶乘的乘积。
由于负数和小数通过减一无法到达递归基例(n==0),代码第七行通过abs()和int()函数将用户的输入转变为非负整数,该程序输出效果如下:
请输入一个整数:23
25852016738884976640000
请输入一个整数:-12
479001600
每次函数调用时,函数参数的副本会临时储存,递归中各函数再运算自己的参数,相互没有影响。当基例结束运算并返回值时,各函数逐层结束运算,向调用者返回计算结果。
递归可以解决很多计算问题。以字符串反转为例,对于用户输入的字符串s,输出反转后的字符串。基本思想是把字符串看作一个递归对象,长字符串由较短字符串组成,每个短字符串也是是一个对象。假如把一个字符串看成仅由两部分组成:首字符和剩余字符串。如果将剩余字符串与首字符交换,就完成了反转整个字符串,代码如下:
def reverse(s):
if s=="":
return s
else:
return reverse(s[1:])+s[0]
str=input("请输入一个字符串:")
print(reverse(str))
观察这个函数的工作过程。s[0]是首字符,s[1:]是剩余字符串,将它们反向连接,可以得到反转字符串。执行这个程序,结果如下:
请输入一个字符串:日照香炉生紫烟
烟紫生炉香照日
3、递归计算方法
递归是一种计算方法,如同数学归纳法。
证明一个与自然数相关的命题P(n)时,数学归纳法采用如下步骤。
(1)证明当n取第一个值时命题成立.
(2)假设当时命题成立,证明当n=+1时命题也成立。
综合(1)和(2),对一切自然数n(n>=),命题P(n)都成立。
递归和数学归纳法都利用了递归原理,本质相同。它们都由链条和基例构成,表达计算问题的解决路线,递归计算方式具体使用函数、分支等语法表达问题,进而利用计算机解决问题。需要注意,递归计算不需要直接给出求解公式,仅需要利用计算机程序表达问题即可,递归会利用计算机的强大算力展开链条,逐次计算得到结果。
六、过程式与函数式编程
编程方法是如何思考、设计、编写程序的方法,与编程语言的语法一样重要。介绍过程式编程和函数式编程两个方法。
过程式编程是一种以计算过程或运算流程为中心的编程方法,也是一种十分自然、符合人类思维方式的编程方法。面对一个计算任务,最直接的描述方式是按照操作流程或步骤进行分解,并编写代码逐步实现流程或步骤所要求的计算功能。过程式编程采用这种方式,以划分计算步骤为主要方法,组织程序代码。
Python语言通过编程语言支持过程式编程。一般来说,采用顺序、分支、循环等逻辑就可以完整描述计算过程。例如,将百分制成绩转化为五分制成绩,代码如下:
#将百分制成绩转换为五分制成绩
score=eval(input("请输入一个百分制成绩:"))
if score>=90.0:
grade="A"
elif score>=80.0:
grade="B"
elif score>=70.0:
grade="C"
elif score>=60.0:
grade="D"
else:
grade="E"
print("对应的五分制成绩是:{}".format(grade))
过程式编程主要关注流程描述。用函数获取用户输入成绩,用分支判断成绩并进行相应转换,最后打印输出。只要将计算步骤梳理清楚,按照步骤编写程序即可。
函数式编程方法是过程式编程的一种演进,亦称模块化编程。函数式编程的主要思想是把程序过程尽量写成一系列函数调用,这能够使代码编写更简洁、更易于理解。函数式编程是软件项目中最常见的编程方式,也是代码复用的主要方式。