一、生成器
在一个函数中经常使用 return 关键字返回数据,但是有时候会使用 yield 关键字返回数据。使用 yield 关键字的函数返回的是一个生成器(generator)对象,生成器对象是一种可迭代对象。
例如,计算平方数列,通常的实现代码如下:
def square(num): # --1
n_list = []
for i in range(1, num + 1):
n_list.append(i * i) # --2
return n_list # --3
for i in square(5): # --4
print(i, end = ' ')
运行结果:
1 4 9 16 25
分析:代码第 1 处定义了一个函数 square(),在函数体内通过循环计算一个数的平方,并将结果保存到一个列表对象 n_list 中。最后返回列表对象,见第 3 处代码。代码第 4 处是遍历返回的列表对象。
在Python中,还可以有更好的解决方案,实现代码如下:
# coding=utf-8
# 代码文件: 函数/yield_generator_test.py
# 生成器:使用 yield 关键字生成一个生成器对象,并返回数据。
def square(num):
for i in range(1, num + 1):
yield i * i # --1
for i in square(5): # --2
print(i, end = ' ')
运行结果:
> python yield_generator_test.py
1 4 9 16 25
分析:上述代码第 1 处使用了 yield 关键字返回平方数,不再需要 return 关键字了。代码第 2 处调用函数 square() 返回的是生成器对象。生成器对象是一种可迭代对象,可迭代对象通过 __next__() 方法获得元素,代码第 2 处的 for 循环能够遍历可迭代对象,就是隐式地调用了生成器的 __next__() 方法获得元素的。
显式地调用生成器的 __next__() 方法,在 Python Shell 中运行示例代码如下:
>>> def square(num):
... for i in range(1, num + 1):
... yield i * i
...
>>> n_seq = square(5)
>>> n_seq.__next__() # --1
1
>>> n_seq.__next__()
4
>>> n_seq.__next__()
9
>>> n_seq.__next__()
16
>>> n_seq.__next__()
25
>>> n_seq.__next__() # --2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
分析:上述代码第 1 处和第 2 处共调用了 6 次 __next__() 方法,但第 6 次调用会抛出 StopIteration 异常,这是因为已经没有元素可迭代了。
生成器函数通过 yield 返回数据,与 return 不同的是,return 语句一次返回所有数据,函数调用结束;而 yield 语句只返回一个元素数据,函数调用不会结束,只是暂停,直到 __next__() 方法被调用,程序继续执行 yield 语句之后的语句代码。这个过程如下图 1 所示。
二、函数的高级内容
Python 的函数是 “一级公民”,因此函数本身是也是一个对象,函数既可用于赋值,也可用于其他函数的参数,还可作为其他函数的返回值。
2.1 使用函数变量
Python 的函数也是一种值:所有函数都是 function 对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。
当把函数赋值给变量之后,接下来程序也可通过该变量来调用函数。例如如下代码:
# coding=utf-8
# 代码文件: 函数/function_var_test.py
# 使用函数变量:将函数赋值给变量的用法
def pow(base, exponent):
result = 1
for i in range(1, exponent + 1):
result *= base
return result
# 将 pow 函数赋值给变量 my_fun,则 my_fun 可被当成 pow 函数使用
my_fun = pow # --1
print(my_fun(3, 4)) # 输出 81
# 定义一个计算面积的函数
def area(width, heigth):
return (width * heigth)
# 将 area 函数赋值给 my_fun,则 my_fun 可被当成 area 使用
my_fun = area # --2
print(area(3, 4)) # 输出 12
运行结果:
> python function_var_test.py
81
12
分析:从上面代码可以看出,程序依次将 pow()、area() 函数赋值给了 my_fun 变量,接下来即可通过 my_fun 变量分别调用 pow()、area() 函数。
通过对 my_fun 变量赋值不同的函数,可以让 my_fun 在不同的时间指向不同的函数,从而让程序更加灵活。由此可见,使用函数变量的好处是让程序更加灵活。
2.2 使用函数作为函数形参
有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定——这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。
Python 支持像使用其他参数一样使用函数参数,例如如下程序。
# coding=utf-8
# 代码文件: 函数/function_param_test.py
# 使用函数作为函数形参的用法
# 定义函数类型的形参,其中 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 i in range(2, n + 1):
result *= i
return result
data = [3, 4, 9, 5, 8]
print('原数据:', data)
# 下面程序代码调用map()函数三次,每次调用时传入不同的函数
print('计算列表元素的平方:')
print(map(data, square))
print('计算列表元素的立方:')
print(map(data, cube))
print('计算列表元素的阶乘:')
print(map(data, factorial))
print(type(map))
运行结果:
> python function_param_test.py
原数据: [3, 4, 9, 5, 8]
计算列表元素的平方:
[9, 16, 81, 25, 64]
计算列表元素的立方:
[27, 64, 729, 125, 512]
计算列表元素的阶乘:
[6, 24, 362880, 120, 40320]
<class 'function'>
分析:从上面介绍不难看出,通过使用函数作为参数可以在调用函数时动态传入函数——实际上就可以动态改变被调用函数的部分代码。
在程序最后添加如下一行:
print(type(map))
分析:从运行结果可以看到,map() 函数是 function 类型,即函数类型,它是以 function 类的形式定义的。
2.3 使用函数作为返回值
前面已经提到,Python 还支持使用函数作为其他函数的返回值。例如如下程序:
# coding=utf-8
# 代码文件: 函数/function_return_test.py
# 使用函数作为返回值的用法
def get_math_func(type):
# 定义一个计算平方的局部函数(嵌套函数)
def square(n): # --1
return n * n
# 定义一个计算立方的函数
def cube(n): # --2
return n * n * n
# 定义一个计算阶乘的函数
def factorial(n): # --3
result = 1
for i in range(2, n + 1):
result *= i
return result
# 返回局部函数
if type == "square":
return square
if type == "cube":
return cube
else:
return factorial
# 调用get_math_func()函数,程序返回一个嵌套函数
math_func = get_math_func("square") # 得到 square 函数
print(math_func(5)) # 输出 25
math_func = get_math_func("cube") # 得到 cube 函数
print(math_func(5)) # 输出 125
math_func = get_math_func("factorial") # 得到 factorial 函数
print(math_func(5)) # 输出 120
print(type(math_func)) # 输出 <class 'function'>
运行结果:
> python function_return_test.py
25
125
120
<class 'function'>
分析:上面程序先定义了一个 get_math_func() 函数,该函数将返回另一个函数。接下来在该函数体内的第1、2、3 处代码分别定义了三个局部函数,最后 get_math_func() 函数会根据所传入的参数,使用这三个局部函数之一作为返回值。
在定义了会返回函数的 get_math_func() 函数之后,接下来程序调用 get_math_func() 函数时即可返回所需的函数,同时从运行结果可以看到,变量 math_func 是函数类型。
三、局部函数与 Lambda 表达式
lambda 表达式是现代编程语言争相引入的一种语法,如果说函数时命名的、方便复用的代码块,那么 lambda 表达式则是功能更加灵活的代码块,它可以在程序中被传递和调用。
3.1 回顾局部函数
在上文的 2.3 节中介绍的 function_return_test.py 程序,该程序中的 get_math_func() 函数将返回三个局部函数之一。该函数代码如下:
def get_math_func(type):
# 定义三个局部函数
...
# 返回局部函数
if type == "square":
return square
if type == "cube":
return cube
else:
return factorial
由于局部函数的作用域仅限于其封闭函数之内,因此这三个局部函数的函数名的作用太有限了——仅仅是在程序的 if 语句中作为返回值使用。一旦离开了 get_math_func() 函数体,这三个局部函数的函数名就失去了意义。
既然局部函数的函数名没有太大的意义,那么就考虑使用 lambda 表达式来简化局部函数的写法,这就是 lambda 表达式引入的目的。
3.2 使用 lambda 表达式代替局部函数
如果使用 lambda 表达式来简化 function_return_test.py 程序,则可以将程序改写如下形式:
# coding=utf-8
# 代码文件: 函数/lambda_test.py
# 使用 lambda 表达式代替局部函数的用法
def get_math_func(type):
# 该函数返回的是 lambda 表达式
if type == "square":
return lambda n: n * n # --1
if type == "cube":
return lambda n: n * n * n # --2
else:
return lambda n: (1 + n) * n / 2 # --3
# 调用get_math_func()函数,程序返回一个嵌套函数
math_func = get_math_func("square") # 得到 square 函数
print(math_func(5)) # 输出 25
math_func = get_math_func("cube") # 得到 cube 函数
print(math_func(5)) # 输出 125
math_func = get_math_func("factorial") # 得到 factorial 函数
print(math_func(5)) # 输出 15.0
print(type(math_func)) # 输出 <class 'function'>
运行结果:
> python lambda_test.py
25
125
15.0
<class 'function'>
分析:在上面第 1、2、3 处的代码中,return 后面的部分使用 lambda 关键字定义的就是 lambda 表达式,Python 要求 lambda 表达式只能是单行表达式。
【注意】由于 lambda 表达式只能是单行表达式,不允许使用更复杂的函数形式,因此上面第 3 处的代码改为计算 1+2+3+...+n 的总和。
lambda 表达式的语法格式如下:
lambda [parameter_list]: 表达式
lambda 表达式的几个要点:
- lambda 表达式必须使用 lambda 关键字定义。
- 在 lambda 关键字之后、冒号左边的是参数列表,可以没有参数,也可以有多个参数。如果有多个参数,则需要用逗号隔开,冒号右边是该 lambda 表达式的返回值。
实际上,lambda 表达式的本质就是匿名的、单行函数体的函数。因此,lambda 表达式可以写成函数的形式。例如,对于如下 lambda 表达式。
lambda x, y: x + y
可改写为如下函数形式。
def add(x, y): return x + y
上面定义函数时使用了简化语法:当函数体只有一行代码时,可以直接把函数的代码放在与函数头同一行的位置。
总体来说,函数比 lambda 表达式的适应性更强,lambda 表达式只能创建简单的函数对象(它只适合函数体为单行的情形)。但 lambda 表达式依然有如下两个用途。
- 对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更简洁。
- 对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高了性能。
下面代码示范了通过 lambda 表达式来调用 Python 内置的 map() 函数。
# coding=utf-8
# 代码文件: 函数/lambda_map.py
# 使用 lambda 表达式来调用Python内置的map()函数
# 传入计算平方的 lambda 表达式作为函数参数
x = map(lambda x: x * x, range(8))
print([i for i in x]) #输出 [0, 1, 4, 9, 16, 25, 36, 49]
# 传入计算平方的 lambda 表达式作为参数
y = map(lambda x: x * x if x % 2 == 0 else 0, range(8))
print([i for i in y]) # 输出 [0, 0, 4, 0, 16, 0, 36, 0]
运行结果:
> python lambda_map.py
[0, 1, 4, 9, 16, 25, 36, 49]
[0, 0, 4, 0, 16, 0, 36, 0]
分析:正如从上面代码所看到的,内置的 map() 函数的第一个参数需要传入函数,此处传入了函数的简化形式:lambda 表达式,这样程序更加简洁,而且性能更好。
四、总结
函数 和 lambda 表达式是 Python 编程的两大核心机制之一。Python 语言既支持面向过程编程,也支持面向对象编程。而函数 和 lambda 表达式就是 Python 面向过程编程的语法基础。
在学习 Python 函数式编程过程中,不仅需要掌握函数定义、函数定义的语法,还需要掌握函数位置参数、关键字参数的区别和用法、形参默认值等高级特性。除此之外,函数也是一个 function 对象,因此函数既可作为其他函数的参数,也可作为其他函数的返回值,通过把函数当成参数传入其他函数,可以让编程变得更加灵活。
Python 的 lambda 表达式只是单行函数的简化版本,因此 lambda 表达式的功能比较简单。
五、编程题
1、定义一个函数,该函数可接收一个 list 作为参数,该函数使用直接选择排序对 list 排序。
# coding=utf-8
# 直接选择排序
def direct_select_sort(list):
list_len = len(list)
for i in range(0, list_len):
for j in range(i + 1, list_len):
if list[i] > list[j]:
list[i], list[j] = list[j], list[i]
s = input("输入待排序数:")
list1 = s.split() # 按空格拆分字符串s
list1 = [int(list1[i]) for i in range(len(list1))] # 将列表每个字符串类型元素转换成int类型
print("排序前:", list1)
direct_select_sort(list1)
print("排序后:", list1)
示例运行结果:
输入待排序数:3 6 1 8 5 -20 100 50 200
排序前: [3, 6, 1, 8, 5, -20, 100, 50, 200]
排序后: [-20, 1, 3, 5, 6, 8, 50, 100, 200]
2、定义一个函数,该函数可接收一个 list 作为参数,该函数使用冒泡排序对 list 排序。
# coding=utf-8
# 冒泡排序
def bubble_sort(list):
list_len = len(list)
for i in range(0, list_len):
is_sorted = True
for j in range(0, list_len - 1 - i):
if list[j] > list[j + 1]:
list[j], list[j + 1] = list[j + 1], list[j]
is_sorted = False
if is_sorted: return # 如果是已排好序的,则直接return
s = input("输入待排序数:")
list1 = s.split() # 按空格拆分字符串s
list1 = [int(list1[i]) for i in range(len(list1))] # 将列表每个字符串类型元素转换成int类型
print("排序前:", list1)
bubble_sort(list1)
print("排序后:", list1)
示例运行结果:
> python test.py
输入待排序数:3 6 1 8 5 -20 100 50 200
排序前: [3, 6, 1, 8, 5, -20, 100, 50, 200]
排序后: [-20, 1, 3, 5, 6, 8, 50, 100, 200]
3、定义一个 is_leap(year) 函数,该函数判断 year 是否为闰年。若是闰年,则返回 True;否则返回 False。
# coding=utf-8
import sys
# 判断闰年
def is_leap(year):
year = int(year)
if (year % 4 == 0) and (year % 100 != 0):
return True
elif year % 400 == 0:
return True
else:
return False
while True:
year = input("请输入一个年份:")
if (year == 'exit') or (year == 'quit'):
sys.exit(0)
print("%s是闰年吗? %s" %(year, is_leap(year)))
示例运行结果:
> python test.py
请输入一个年份:2022
2022是闰年吗? False
请输入一个年份:2020
2020是闰年吗? True
请输入一个年份:quit
4、定义一个 count_str_char(my_str) 函数,该函数返回参数字符串中包含有多少个数字、多少个英文字母、多少个空白字符、多少个其他字符。
# coding=utf-8
import sys
def count_str_char(my_str):
if my_str == None: return 0, 0, 0, 0
digit_num, char_num, space_num, others_num = 0, 0, 0, 0
for c in my_str:
if c.isdigit(): digit_num += 1
elif c.isalpha(): char_num += 1
elif c.isspace(): space_num += 1
else: others_num += 1
return digit_num, char_num, space_num, others_num
while True:
string = input("请输入一个字符串: ")
if (string == 'exit') or (string == 'q'):
sys.exit(0)
digit_num, char_num, space_num, others_num = count_str_char(string)
print('数字个数:', digit_num)
print('字母个数:', char_num)
print('空格个数:', space_num)
print('其他字母个数:', others_num)
示例运行结果:
> python test.py
请输入一个字符串: Copyright (C), 2001-2018, yeeku.H.Lee
数字个数: 8
字母个数: 19
空格个数: 3
其他字母个数: 7
请输入一个字符串: q
5、定义一个 fn(n) 函数,该函数返回 1~n 的立方和,即求 。
# coding=utf-8
def fn(n):
if n < 1:
print("输入错误!")
exit()
sum = 0
for i in range(1, n + 1):
sum += i ** 3
return sum
n = int(input("请输入一个整数:"))
result = fn(n)
print("1~%d的立方和是:%d" %(n, result))
6、定义一个 fn(n) 函数,该函数返回 n 的阶乘。
# coding=utf-8
def fn(n):
if n < 1:
print("输入错误!")
exit()
if n == 1:
return 1
else:
return n * fn(n-1) # 函数递归调用
n = int(input("请输入一个整数:"))
result = fn(n)
print("%d的阶乘是:%d" %(n, result))
7、定义一个函数,该函数可接收一个 list 作为参数,该函数用于去除 list 中重复的元素。
假设:list 列表中的元素是 int 类型的,实现代码如下:
# coding=utf-8
def remove_duplicate(list):
if list == None: return None
new_list = []
[new_list.append(list[i]) for i in range(len(list)) if list[i] not in new_list] # 列表推导式
return new_list
string = input("请输入列表元素(用空格隔开):")
my_list = string.split() # 按空格拆分字符串string
my_list = [int(my_list[i]) for i in range(len(my_list))] # 将列表每个字符串类型元素转换成int类型
print(remove_duplicate(my_list))
示例运行结果:
> python test.py
请输入列表元素(用空格隔开):15 12 13 15 12 16
[15, 12, 13, 16]
8、定义一个 fn(n) 函数,该函数返回一个包含 n 个不重复的 0~100 之间整数的元组。
# coding=utf-8
import random
def fn(n):
i, tmp_list = 0, []
while True:
num = random.randint(0, 100)
# 如果随机数不包含在列表中,则保存
if num not in tmp_list:
tmp_list.append(num)
i += 1
if i == n:
break
# 将列表转出元组返回
return tuple(tmp_list)
n = int(input("请输入整数n:"))
print(fn(n))
9、定义一个 fn(n) 函数,该函数返回一个包含 n 个不重复的大写字母的元组。
# coding=utf-8
import random
def fn(n):
i, tmp_list = 0, [] # 初始化变量和列表
while True:
# 大写字母A的ASCII值为65,大写字母Z的ASCII值为90
num = random.randint(65, 65 + 25)
# 如果随机数不在列表中,则保存
ch = chr(num) # 将整数转换成ASCII字符类型
if ch not in tmp_list:
tmp_list.append(ch)
i += 1
if i == n:
break
# 将列表转换成元组返回
return tuple(tmp_list)
n = int(input("请输入整数n:"))
print(fn(n))
10、定义一个 fn(n) 函数,其中 n 表示输入 n 行 n 列的矩阵(数的方阵)。在输出时,先输出 n 行 n 列的矩阵,再输出该矩阵的转置矩阵。例如,当参数输入 3 时,先输出:
1 2 3
4 5 6
7 8 9
再输出:
1 4 7
2 5 8
3 6 9
# coding=utf-8
def fn(n):
# 输出初始矩阵
for i in range(n):
for j in range(n):
print('%4d ' %(i * n + j + 1), end = '')
print()
print('-' * (5 * n)) # 打印一行分隔符
# 输出转置矩阵
for i in range(n):
for j in range(n):
print('%4d ' %(j * n + i + 1), end = '')
print()
n = int(input("请输入整数n:"))
print(fn(n))
示例运行结果:
> python test.py
请输入整数n:3
1 2 3
4 5 6
7 8 9
---------------
1 4 7
2 5 8
3 6 9
None
> python test.py
请输入整数n:4
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
--------------------
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
None
> python test.py
请输入整数n:5
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
-------------------------
1 6 11 16 21
2 7 12 17 22
3 8 13 18 23
4 9 14 19 24
5 10 15 20 25
None
参考
《Python从小白到大牛(第1版-2018).pdf》第10章 - 函数式编程
《疯狂Python讲义(2018.12).pdf》第5章 - 函数和lambda表达式
《Python编程:从入门到实践(2016.7).pdf》第8章 - 函数