6. 函数
函数是执行特定任务的一段代码,程序通过将一段代码定义成函数,并为该函数指定一个函数名,这样即可在需要的时候多次调用这段代码。因此,函数是代码复用的重要手段。在 Python 中,用 lambda 表达式创建匿名函数能够让程序更加简洁。
6.1. 函数入门
函数就是 Python 程序的重要组成部分,一个 Python 程序可以由很多个函数组成。
6.1.1. 理解函数
通俗来讲,函数就是将一段特定功能的代码放在一起,并为其取了一个名字,即函数名,在后续需要使用这段代码的功能时直接调用这个名字。正如在列表中使用到了取长度函数 len()
。
通常,函数可以接收零个或多个参数,也可以返回零个或多个值。从函数使用者的角度来看,函数就像一个黑盒,程序将零个或多个参数传入这个黑盒,该黑盒经过一番处理即可返回零个或多个值。具体流程如下图所示:
6.1.2. 定义函数和调用函数
在使用函数之前必须先定义函数,定义函数的语法格式如下:
def func_name(params)
statements
[return [values]]
Python 声明函数时使用 def
关键字,对函数语法格式的详细说明如下:
func_name
称为函数名:函数名由一个或多个有意义的单词连缀而成,每个单词的字母全部小写,单词与单词之间使用下画线分隔params
称为形参列表:用于定义函数可以接收的参数。形参列表由多个形参名组成,多个形参名之间以英文逗号,
隔开
在函数体中多条可执行语句之间有严格的执行顺序,排在函数体前面的语句总是先执行,排在函数体后面的语句总是后执行。使用方法如下代码所示:
# 定义一个函数,声明两个形参
def my_max(x, y):
# 定义一个变量 z , 该变量等于 x、y 中较大的值
z = x if x > y else y
# 返回变量 z 的值
return z
# 定义一个函数,声明一个形参
def say_hello(name):
return "Hello, " + name
# 调用 my_max() 函数,将函数返回值赋值给 result 变量
result = my_max(6, 9) # 9
print(result)
# 调用 say_hello() 函数,直接输出函数的返回值
print(say_hello("felixzzy")) # Hello, felixzzy
如上,在函数体中使用 return
语句显式地返回一个值,return
语句返回的值既可是有值的变量,也可是一个表达式。
6.1.3. 为函数提供文档
函数像一个黑盒,因此一个编写完善的函数需要有详细的说明文档,在我们设计函数时,也需要为函数编写说明文档,只要把一段字符串放在函数声明之后、函数体之前,这段字符串将被作为函数的部分,这个文档就是函数的说明文档。
程序既可通过 help()
函数查看函数的说明文档,也可通过函数的 __doc__
属性访问函数的说明文挡。详细使用方法如下代码所示:
def my_max(x, y):
'''
获取两个数值之间较大数的函数
my_max(x, y)
返回 x、y 两个参数之间较大的那个数
'''
# 定义一个变量 z,该变量等于 x、y 中较大的值
z = x if x > y else y
# 返回变量 z 的值
return z
# 使用 help() 函数查看 my_max 的帮助文档
help(my_max)
print(my_max.__doc__)
上面程序使用多行字符串的语法为 my_max()
函数编写了说明文档,既可以通过 help()
函数查看该函数的说明文档,也可通过 __doc__
属性访问该函数的说明文档。
运行上面代码,可以看到如下运行结果。
Help on function my_max in module __main__ :
my_max(x, y)
获取两个数值之间较大数的函数
my_max(x, y)
返回 x、y 两个参数之间较大的那个数
获取两个数值之间较大数的函数
my_max(x, y)
返回 x、y 两个参数之间较大的那个数
6.1.4. 多个返回值
如果程序需要有多个返回值,则既可将多个值包装成列表之后返回,也可直接返回多个值。 如果 Python 函数直接返回多个值, Python 会自动将多个返回值封装成元组。使用方法如下代码所示:
def sum_and_avg(list):
sum = 0
count = 0
for e in list:
# 如果元素 e 是数值
if isinstance(e, int) or isinstance(e, float):
count += 1
sum += e
return sum, sum / count
my_list = [20, 15, 2.8, 'a', 35, 5.9, 1.8]
# 获取 sum_and_avg 函数返回的多个值,多个返回值被封装成元组
tp = sum_and_avg(my_list)
print(type(tp)) # <class 'tuple'>
上面代码中函数的返回值被自动封装成元组。
6.1.5. 递归函数
在一个函数内调用该函数自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。一个简单的例子是求序列的和:即 f(0) = 0,f(n) = f(n-1)+1,具体的代码如下所示:
def my_sum(n):
if n == 0:
return 0
else:
return my_sum(n-1) + n
result = my_sum(5)
print(result) # 15
6.2. 函数的参数
在定义 Python 函数时可定义形参,即形式参数,这些形参的值要等到调用时才能确定下来,由函数的调用者负责为形参传入参数值。
6.2.1. 关键字( keyword )参数
Python 允许在调用函数时通过名字来传入参数值,因此, Python 函数的参数名应该具有更好的语义。按照形参位置传入的参数被称为位置参数。如果使用位置参数的方式来传入参数值,则必须严格按照定义函数时指定的顺序来传入参数值,如果根据参数名来传入参数值,则无须遵守定义形参的顺序,这种方式被称为关键字参数。使用方法如下代码所示:
# 计算面积
def area(width, height):
return width * height
# 根据位置传入参数值
print(area(4, 5))
# 根据关键字参数传入参数值
print(area(width=4, height=5))
# 根据关键字参数传入参数值可以交换位置
print(area(height=5, width=4))
# 部分使用关键字参数,部分使用位置参数
print(area(4, height=5))
如果希望在调用函数时混合使用关键字参数和位置参数,则关键字参数必须位于位置参数之后,否则会出错,如下代码所示:
# 错误代码
print(area(width=4, 5))
运行上面代码,将会提示如下错误 。
SyntaxError: positional argument follows keyword argument
6.2.2. 参数默认值
在某些情况下,需要在定义函数时为一个或多个形参指定默认值,这样在调用函数时就可以省略为该形参传入参数值,而是直接使用该形参的默认值。为形参指定默认值的语法格式如下:
形参名=默认值
仍以上面的计算面积的代码为例,参数默认值的使用方法如下代码所示:
# 计算面积
def area(width=4, height=5):
return width * height
# 根据关键字参数传入新的参数值
print(area(width=5, height=6)) # 30
# 使用默认值
print(area()) # 20
从上面的例子来看,当传入新的参数值时,使用新的参数值,如果不传入参数值,则使用默认值。
6.2.3. 个数可变的参数
Python 允许定义个数可变的参数,这样就可以在调用函数时传入任意多个参数。只需在形参前添加一个星号 *
,这样该参数就可以接收多个参数值,多个参数值被当成元组传入。使用方法如下代码所示:
def cs_language(*lan):
print(type(lan)) # <class 'tuple'>
print(lan) # ('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python')
cs_language('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python')
Python 中一个函数最多只能有一个个数可变的
*
的参数。
当第一个参数是一个个数可变的参数时,后续的参数在传入参数时,需要使用关键字参数,如下代码所示:
def cs_language(*lan, num):
print(type(lan)) # <class 'tuple'>
print(lan) # ('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python')
print(num) # 6
cs_language('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python', num=6)
Python 中的函数还可以使用关键字参数,即在参数前面添加两个星号 **
,此时 Python 会将这种关键字参数当成字典。如下代码所示:
def cs_language(*lan, num, **test):
print(type(lan)) # <class 'tuple'>
print(lan) # ('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python')
print(num) # 6
print(type(test)) # <class 'dict'>
print(test) # {'test1': 1, 'test2': 2}
cs_language('Ruby', 'Go', 'Swift', 'Kotlin', 'Erlang', 'Python', num=6, test1=1, test2=2)
6.2.4. 函数的参数传递机制
前面章节中介绍了 Python 中的基本数据类型,包括整型, String 类型以及 List 类型等等,在 Python 中数据类型又可以分为可变类型和不可变类型:
- 可变类型:变量赋值后可修改其中的元素,如 List 类型
- 不可变类型:变量赋值后不可以修改,注意,如整型是重新赋值而不是修改
在 Python 中,函数的参数传递也是根据数据类型的不同而有不同的结果:
- 可变类型:如列表,字典等,相当于引用传递
- 不可变类型:如整数、字符串、元组等,相当于值传递
具体使用方法如下代码所示:
def fun1(a):
# 不可变类型的形参地址
print("不可变类型形参原地址:", id(a))
a=10
print("不可变类型形参修改后地址:", id(a))
def fun2(a):
# 可变类型的形参地址
print("可变类型形参原地址:", id(a))
# 修改部分元素
a[1] = 10
print("可变类型形参修改后地址:", id(a))
# 定义不可变类型
a=1
print("不可变类型原地址:", id(a))
fun1(a)
print(a)
b = [1,2,3,4]
print("可变类型原地址:", id(b))
fun2(b)
print(b)
运行上述代码,有如下的输出:
不可变类型原地址: 4369074928
不可变类型形参原地址: 4369074928
不可变类型形参修改后地址: 4369075216
1
可变类型原地址: 4371270976
可变类型形参原地址: 4371270976
可变类型形参修改后地址: 4371270976
[1, 10, 3, 4]
此处 id()
函数的作用是查看变量的地址,从运行结果可以发现在参数传递的过程中,对不可变类型形参修改后,其实是指向了一个新的地址,并不会改变实参的值,而可变类型形参还是指向原先的地址,实际上是在实参上做了修改。
6.2.5. 变量作用域
在程序中定义一个变量时,这个变量是有作用范围的,变量的作用范围被称为它的作用域。根据定义变量的位置不同,变量分为以下两种:
- 局部变量。在函数中定义的变量,包括参数,都被称为局部变量。
- 全局变量。在函数外面、全局范围内定义的变量,被称为全局变量。
每个函数在执行时,系统都会为该函数分配一块“临时内存空间”,所有的局部变量都被保存在这块临时内存空间内。当函数执行完成后,这块内存空间就被释放了,这些局部变量也就失效了,因此离开函数之后就不能再访问局部变量了。
全局变量意味着它们可以在所有函数内被访问。
6.3. 局部函数
Python 支持在函数体内定义另一个函数,这种被放在函数体内定义的函数称为局部函数。默认情况下,局部函数对外部是隐藏的,局部函数只能在其父函数内有效。当然,也可以通过其父函数返回局部函数,以便在其他作用域中使用局部函数。使用方法如下代码所示:
# 定义函数,该函数会包含局部函数
def get_math_func(type, nn):
# 定义一个计算平方的局部函数
def square(n):
return n * n
# 定义一个计算立方的局部函数
def cube(n):
return n * n * n
# 定义一个计算阶乘的局部函数
def factorial(n):
result = 1
for index in range(2, n+1):
result *= index
return result
# 调用局部函数
if type == "square":
return square(nn)
elif type == "cube":
return cube(nn)
else:
return factorial(nn)
print(get_math_func("square", 3)) # 输出 9
print(get_math_func("cube", 3)) # 输出 27
print(get_math_func("", 3)) # 输出 6
如上代码中定义了一个 get_math_func()
函数,并在该函数内定义了三个局部函数,而 get_math_func()
函数根据参数选择调用不同的局部函数。
6.4. 函数的其他用法
在 Python 中,函数本身也是一个对象,函数既可用于赋值,也可用作其他函数的参数,还可作为其他函数的返回值。
6.4.1. 使用函数变量
Python 的函数是一个对象,这样就可以把函数赋值给变量,当把函数赋值给变量之后,程序就可以通过该变量来调用函数。使用方法如下代码所示:
# 定义一个计算面积的函数
def area(width, height):
return width * height
# 将 area 函数赋值给 my_fun ,则 my_fun 可被当成 area 使用
my_fun = area
print(my_fun(3, 4)) # 输出 12
在上述代码中,将函数 area()
赋值给变量 my_fun
,在程序中就可以通过调用变量 my_fun
实现函数 area()
的功能。
6.4.2. 使用函数作为函数形参
既然函数可以赋值给变量,那么函数自然也可以作为另一个函数的形参,这样就可以通过该形参动态改变函数的功能,如下代码通过形参实现不同的计算逻辑:
# 定义函数类型的形参,其中 fn 是一个函数
def map(data, fn):
result = []
# 遍历 data 列表中的每个元素,并用 fn 函数对每个元素进行计算
# 然后将计算结果作为新数组的元素
for e in data:
result.append(fn(e))
return result
# 定义一个计算平方的函数
def square(n):
return n * n
# 定义一个计算立方的函数
def cube(n):
return n * n * n
# 定义一个计算阶乘的函数
def factorial(n):
result = 1
for index in range(2, n+1):
result *= index
return result
data = [3, 4, 9, 5, 8]
print(data)
# 下面程序代码调用 map() 函数三次,每次调用时传入不同的函数
print(map(data, square)) # [9, 16, 81, 25, 64]
print(map(data, cube)) # [27, 64, 729, 125, 512]
print(map(data, factorial)) # [6, 24, 362880, 120, 40320]
print(type(map)) # <class 'function'>
如上代码中定义了一个 map()
函数,该函数的第二个参数是一个函数类型的参数,这意味着每次调用函数时可以动态传入一个函数,从而实现不同的计算逻辑。
6.4.3. 使用函数作为返回值
Python 还支持使用函数作为其他函数的返回值,如下代码所示:
def get_math_func(type):
#定义一个计算平方的局部函数
def square(n):
return n * n
# 定义一个计算立方的局部函数
def cube(n):
return n * n * n
# 定义一个计算阶乘的局部函数
def factorial(n):
result = 1
for index in range(2, n+1):
result *= index
return result
# 返回局部函数
if type == "square":
return square
elif type == "cube":
return cube
else:
return factorial
# 调用 get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube") # 得到 cube 函数
print(math_func(5)) # 输出 125
math_func = get_math_func("square") # 得到 square 函数
print(math_func(5)) # 输出 25
math_func = get_math_func("other") # 得到 factorial 函数
print(math_func(5)) # 输出 120
如上代码中定义了一个 get_math_func()
函数,在该主函数中又定义了三个局部函数 square()
,cube()
和 factorial()
,通过形参 type
确定主函数返回的局部函数。
6.5. 局部函数与 lambda 表达式
lambda 表达式是现代编程语言争相引入的一种语法,如果说函数是命名的、方便复用的代码块,那么 lambda 表达式则是功能更灵活的代码块,它可以在程序中被传递和调用。
6.5.1. 匿名函数
匿名函数是指没有名字的函数,它主要应用在需要一个函数、但是又不想费神去命名这个函数的场合。回顾在局部函数一节中的函数 get_math_func()
,在该函数中定义了三个局部函数 square()
, cube()
和 factorial()
,由于局部的函数只能在函数 get_math_func()
中调用,因此使用完整的函数定义是不必要的,此时可以直接使用匿名函数。
6.5.2. 使用 lambda 表达式创建匿名函数
在Python中,使用 lambda 表达式创建匿名函数,其语法格式如下:
lambda [parameter_list]:expression
使用 lambda 表达式重新修改 get_math_func()
函数,代码如下所示:
# 定义函数,该函数包含匿名函数
def get_math_func(type):
# lambda 匿名函数
if type == "square": # 平方
return lambda n : n*n
elif type == "cube": # 立方
return lambda n : n*n*n
else: # 累加
return lambda n : (1+n)*n/2
print(get_math_func("square")(3)) # 输出 9
print(get_math_func("cube")(3)) # 输出 27
print(get_math_func("")(3)) # 输出 6.0
lambda 表达式只能是单行表达式,不允许使用更复杂的函数形式,因此使用 lambda 表达式表示原先的阶乘的操作相对比较困难。
6.6. 本章小结
本章介绍了函数的使用方法和 lambda 表达式,包括定义函数、调用函数的语法。同时,函数也是一个 function 对象,因此函数既可作为其他函数的参数,也可作为其他函数的返回值,通过把函数当成参数传入其他函数,可以让编程变得更加灵活。 Python 的 lambda 表达式只是单行函数的简化版本,因此 lambda 表达式的功能比较简单。
本章需要掌握知识点:
- 函数的定义和调用
- 形参的各种用法,如关键字参数,个数可变形参等
- 理解函数的参数传递机制
- 理解局部函数以及 lambda 表达式