在一个函数中经常使用 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 "
" , line 1, in - StopIteration
- >>>
分析:上述代码第 1 处和第 2 处共调用了 6 次 __next__() 方法,但第 6 次调用会抛出 StopIteration 异常,这是因为已经没有元素可迭代了。
生成器函数通过 yield 返回数据,与 return 不同的是,return 语句一次返回所有数据,函数调用结束;而 yield 语句只返回一个元素数据,函数调用不会结束,只是暂停,直到 __next__() 方法被调用,程序继续执行 yield 语句之后的语句代码。这个过程如下图 1 所示。
Python 的函数是 “一级公民”,因此函数本身是也是一个对象,函数既可用于赋值,也可用于其他函数的参数,还可作为其他函数的返回值。
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 在不同的时间指向不同的函数,从而让程序更加灵活。由此可见,使用函数变量的好处是让程序更加灵活。
有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定——这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。
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 类的形式定义的。
前面已经提到,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)) # 输出
运行结果:
- > 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 表达式则是功能更加灵活的代码块,它可以在程序中被传递和调用。
在上文的 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 表达式引入的目的。
如果使用 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)) # 输出
运行结果:
- > 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 x, y: x + y
可改写为如下函数形式。
def add(x, y): return x + y
上面定义函数时使用了简化语法:当函数体只有一行代码时,可以直接把函数的代码放在与函数头同一行的位置。
总体来说,函数比 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 表达式的功能比较简单。
- # 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]
- # 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]
- # 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
- # 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
。- # 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))
- # 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))
假设: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]
- # 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))
- # 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))
- 1 2 3
- 4 5 6
- 7 8 9
再输出:
- 1 4 7
- 2 5 8
- 3 6 9
- # coding=utf-8
-
- import random
-
- 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章 - 函数