文章目录
函数的定义
- 函数是为实现一个特定功能而组合在一起的语句集,可以用来定义可重用代码、组织和简化代码。
- 函数定义格式如下:
def 函数名(形式参数):
函数体
无参数的函数
- 例子:定义一个函数,函数的功能是打印一行Hello World!,并调用该函数。
- 程序代码:
- 注意看:
- 问题:如果要打印出Hello!和How are you?,则不能使用此函数。如何改进此函数使之能打印出其他字符串呢?
- 注意看:
- 形式参数是根据需要定义的。当调用带形式参数的函数时,就将一个值传递给形参,这个值称为实际参数,简称为实参。字符串“Hello!”和“How are you?”都是实参。
带返回值的函数
- 一些函数可能只完成要求的操作而无返回值,而另一些函数可能需要返回一个计算结果给调用者。如果函数有返回值,则被称为带返回值的函数,使用关键字return来返回一个值。执行return语句同时意味着函数的终止。
- 代码案例(定义一个函数,其功能是求正整数的阶乘,并利用该函数求解6!、16!和26!的结果。):
- 注意看:
函数的调用
-
函数的定义是通过参数和函数体决定函数能做什么,并没有被执行。
-
而函数一旦被定义,就可以在程序的任何地方被调用。
-
当调用一个函数时,程序控制权就会转移到被调用的函数上,真正执行该函数;
-
执行完函数后,被调用的函数就会将程序控制权交还给调用者。
-
以上面求阶乘的案例说明函数执行过程:
-
函数体中还可以调用其他函数,在这种情况下,函数又是怎样调用的呢?
-
代码案例(利用计算正整数阶乘的函数,编写求阶乘和1!+2!+…+n!的函数,利用该函数求1!+2!+3!+4!+5!的和。):
-
注意看:
形参与实参
- 在函数定义中,函数名后面括号中列出的参数称为形式参数,简称形参。
- 如果形参的个数超过1个,各参数之间用逗号隔开。
- 在定义函数时,函数的形参不代表任何具体的值,只有在函数调用时,才会有具体的值赋给形参。
- 调用函数时传入的参数称为实际参数,简称实参。
- 代码案例(编写函数,利用辗转相除法求两个自然数的最大公约数,并利用该函数求25与45的最大公约数,36与12的最大公约数。
):- 辗转相除法的算法如下:
1. 两个自然数x和y,保证x≥y,否则交换x与y的值;
2. 计算x除以y的余数r;
3. 若r==0,则y就是最大公约数;否则用y替换x,用r替换y,重复步骤2。
- 辗转相除法的算法如下:
函数的返回
- 函数的执行结果通过返回语句return返回给调用者。
- 函数调用时的参数传递实现了从函数外部向函数内部输入数据,而函数的返回则解决了函数向外部输出信息的问题。
- 函数体中不一定有return语句。
- 如果一个函数的定义中没有return语句,系统将自动在函数体的末尾插入return None语句。
- 代码案例(定义一个函数求圆的面积,然后调用它打印出给定半径的圆的面积):
上述代码只是求出给定半径的圆的面积,如果要再同时求出圆的周长又该如何编写程序呢?返回值又有什么不同呢? - 代码案例(定义一个函数,函数的功能是求圆的面积和周长,然后调用它打印出给定半径的圆的面积和周长。):
多个返回值的处理
-
当一个函数需要返回多个值时,在return语句之后跟上多个需要返回的表达式或变量,这些表达式的值和变量将共同构成一个元组返回给调用者。
- 代码改进
- 代码改进
-
一般来说,函数执行完所有步骤之后才得出计算结果并返回,return语句通常出现在函数的末尾。
-
有时我们希望改变函数的正常流程,在函数到达末尾之前就终止并返回
-
例如当函数检查到错误的数据时就没有必要继续执行。可以修改第一个p函数,检查输入,如果不是正数则退出函数,否则对数据进行处理。代码如图所示:
位置参数与关键参数
-
当调用函数时,需要将实参传递给形参。
-
参数传递时有两种方式:位置参数和关键参数。
- 位置参数是指按照参数的位置来传递
- 关键参数是指按照参数赋值的形式来传递
-
当使用位置参数时,实参和形参在顺序、个数和类型上必须一一匹配。前面示例中,调用带参数的函数时均使用位置参数的方式。
-
代码案例(改进sayHello函数,使之能输出多行字符串。调用该函数打印3行“Hello!”字符串。):
-
代码案例(改进sayHello函数,使之能输出多行字符串。调用该函数打印3行“Hello!”字符串。
):
-
在语句
sayHello("Hello!",3)
中,按照形参定义的顺序与实参排列顺序对应关系,将字符串"Hello!"传递给s,将3传递给n。 -
如果使用
sayHello(3,"Hello!")
,根据对应关系,则表示将3传递给s,将字符串"Hello!"传递给n,程序会出现如下错误:
cannot concatenate ‘str’ and ‘int’ objects -
又来一个代码(编写一个函数,有三个形式参数,其中两个传递字符分别作为开始字符和结束字符,打印出两个字符之间的所有字符,每行打印的字符个数由第三个形式参数指定。
):
默认参数
-
函数的形参可以设置默认值。这种形参通常称为默认参数。
-
调用函数时如果不为默认参数提供值,这些参数就使用默认值;如果在调用时有实参,则将实参的值传递给形参,形参定义的默认值将被忽略。
-
具有默认参数值的函数定义格式如下:
def 函数名(非默认参数, 形参名=默认值, ……) : 函数体
-
函数定义时,形式参数中非默认参数与默认参数可以并存,但非默认参数之前不能有默认参数。
def sayHello(s,n=2)
是有效的def sayHello(s="Hello!",n)
是无效的。
-
案例代码:
-
在函数调用中,通过“变量名=值”的“键-值”形式将实参传递给形参,使得参数可以不按顺序来传递,让函数参数的传递更加清晰、易用。采用这种方式传递的参数称为关键参数
-
看看代码:
可变长度参数
-
在前面的函数介绍中,我们知道一个形参只能接收一个实参的值。
-
其实在Python中,函数可以接收不定个数的参数,即用户可以给函数提供可变长度的参数。这可以通过在形式参数前面使用标识符“*”来实现。
-
代码案例(编写一个函数,接收任意个数的参数并打印出来):
-
在形式参数args前面有一个标识符“*”,表明形参args可以接收不定个数的参数。
-
不管传递几个参数到args,都是将接收的所有参数按照次序组合到一个元组上。
-
也可以不传递实参,形参将得到空元组。
-
代码案例(编写一个函数,接收任意个数的数字参数并求和):
-
在Python中,有很多内置函数也使用可变参数函数,如max和min都可以接收任意个数的参数。
-
可变长度的参数也可以和其他普通参数联合使用,这时一般将可变长度参数放在形参列表的最后。
-
代码案例:
-
Python的函数形参中还提供了一种参数名前面加标识符“**”的方式,用来引用一个字典。
-
函数调用者须以关键参数的形式为其赋值,形式参数得到一个以关键参数中变量名为key,右边表达式值为value的字典。
-
代码案例:
-
以“ ** ”为前缀的可变长度参数、以“ * ”为前缀的可变长度参数、普通参数在函数定义中可以混合使用。这时,普通参数放在最前面,其次是以“ * ”为前缀的可变长度参数,最后是以“ ** ”为前缀的可变长度参数。
-
代码案例:
def all_6(a,b,*aa,**bb):
print(a)
print(b)
print(aa)
print(bb)
all_6(1,2,3,4,5,xx="a",yy="b",zz=2)
- 程序运行结果:
- 1
- 2
- (3, 4, 5)
- {‘xx’: ‘a’, ‘yy’: ‘b’, ‘zz’: 2}
序列和字典作为参数
- 函数中形参与实参均为序列;
- 如果函数中形参是n个单变量,则在实参的序列变量名前加“*”,要求实参序列中的元素个数与单变量形参个数相同;
- 如果实参中普通变量与序列变量混用,则以“*”为前缀的序列变量放置在实参的最后。
- 对snn2()函数疑惑的点:其实也就是通过键找值。其次就是可迭代对象可以遍历呀!!!
- (看这三个图的第一张)以序列变量名直接作为实参调用函数snn1。在函数snn1的定义中,形参也是序列。snn1函数中实参可以是列表或者元组,功能是求列表或者元组中各元素的和。snn2函数中形参是字典变量,功能是求字典中值的和,调用时实参也是字典变量名或字典。
- 代码案例(以单变量为形参、以序列为实参传递参数案例):
可变对象参数与不可变对象参数
-
函数调用者将实参以变量参数的形式传递给函数形参,函数体内部对该形参进行修改,函数调用结束后,函数调用者相应的实参变量是否发生了变化?这要分两种情况来讨论。
- 如果传递的参数变量是一个不可变对象的变量,函数内部对该形参变量的任何修改都不会对调用者的实参产生影响。
- 如果传递的参数变量是一个可变对象的变量,函数内部对该形参变量的修改都会反馈到实参变量,函数调用结束后,实参也能看到相同的修改效果。
-
代码案例(函数内部对可变对象参数和不可变对象参数的修改导致实参变化的情况):
-
fun函数有两个参数x和y,函数调用时,将整数变量a传递给x,列表变量b传递给y。整数是不可变对象,列表是可变对象。
-
函数体内对x的修改使得x指向了新的对象11,而实参a仍然指向原来的对象1。所以调用结束后a的值保持不变。
-
函数体内在y所指向的列表后添加一个元素,由于列表是可变对象,直接加在原对象的后面,y所指向的对象和b所指向的对象地址都没有发生变化,都指向原来的对象。所以列表变量y添加了元素,也就是列表变量b添加了元素。因此,函数调用结束后,b的值也跟随函数内y的值一样发生了变化。
lambda函数
-
lambda函数是一个匿名函数,有时也被称为lambda表达式,比def格式的函数定义简单很多。Lambda函数可以接收任意多个参数,但只返回一个表达式的值。lambda中不能包含多个表达式。
-
lambda函数的定义格式为:
- lambda 形式参数 : 表达式
-
其中形式参数可以有多个,它们之间用逗号隔开。表达式只有一个。返回表达式的计算结果。
-
以下例子中表达式左边的变量相当于给lambda函数定义了一个函数名。可以将此变量名作为函数名来调用该lambda函数。
-
lambda表达式通常以匿名的方式出现,如以下例子所示:
-
小小科普:
- filter函数是Python中常用的内置函数,调用无需加载库,直接使用即可。它主要用来根据特定条件过滤迭代器中不符合条件的元素,返回一个惰性计算的filter对象或迭代器。需要用list函数进行转换,才能得到符合的条件元素组成的新列表。
递归
-
函数内部可以调用其他函数。如果一个函数在内部直接或间接地调用自己本身,这是一种递归的方法。
-
代码案例(利用递归的思想来实现阶乘函数,然后调用该函数求正整数的阶乘):
- 如果我们要求4!,根据阶乘定义,4!=4×3!,而3!=3×2!,2!=2×1! ,1!=1,依此回推,2!=2×1!= 2×1=2 ,3!=3×2! =3×2=6,4!=4×3! =4×6=24,这样我们就能求得4!。
-
把这种思想融入程序代码中。
-
程序的递归调用过程:
-
一个递归调用可能导致更多的递归调用,要终止一个递归调用,必须最终递归到满足一个终止条件。
-
递归调用是通过栈来实现的,分为递推过程和回归过程。
- 每调用一次自身,把当前参数压栈,直到达到递归终止条件,这个过程叫递推过程。
- 当达到递归终止条件时,从栈中一一弹出当前的参数进行计算,直到栈为空,这个过程叫回归过程。
- 图中,步骤1-4是递推过程,步骤5-8是回归过程。
-
一个递归调用当达到终止条件时,就将结果返回给调用者。然后调用者进行计算并将结果返回给它自己的调用者。这个过程持续进行,直到结果被传回原始的调用者为止。
-
因此在编写递归函数的时候必须满足以下两点:
- 有明确的递归终止条件及终止时的值;
- 能用递归形式表示,并且向终止条件的方向发展。
-
请思考,如果按如下方式编写fac函数会出现什么问题?
def fac(n): s=n*fac(n-1) return s
-
这个递归没有终止条件,会永远递归调用下去,理论上程序也永不停止。这种现象被称为无限递归。
-
在大多数程序环境中,无限递归的函数并不会真的永远执行,Python会在递归深度到达上限时报告一个错误信息。
-
代码案例(猴子吃桃):
- 一天猴子摘了若干桃子,每天吃现有桃子数的一半多1个,第7天早上只剩下1个桃子,问猴子一共摘了多少个桃子?试用迭代和递归两种方法实现。
- 一天猴子摘了若干桃子,每天吃现有桃子数的一半多1个,第7天早上只剩下1个桃子,问猴子一共摘了多少个桃子?试用迭代和递归两种方法实现。