• Python学习笔记 - 函数和lambda表达式 (2)


    一、生成器

            在一个函数中经常使用 return 关键字返回数据,但是有时候会使用 yield 关键字返回数据。使用 yield 关键字的函数返回的是一个生成器(generator)对象,生成器对象是一种可迭代对象。

    例如,计算平方数列,通常的实现代码如下:

    1. def square(num): # --1
    2. n_list = []
    3. for i in range(1, num + 1):
    4. n_list.append(i * i) # --2
    5. return n_list # --3
    6. for i in square(5): # --4
    7. print(i, end = ' ')

    运行结果:

    1 4 9 16 25

    分析:代码第 1 处定义了一个函数 square(),在函数体内通过循环计算一个数的平方,并将结果保存到一个列表对象 n_list 中。最后返回列表对象,见第 3 处代码。代码第 4 处是遍历返回的列表对象。

    在Python中,还可以有更好的解决方案,实现代码如下:

    1. # coding=utf-8
    2. # 代码文件: 函数/yield_generator_test.py
    3. # 生成器:使用 yield 关键字生成一个生成器对象,并返回数据。
    4. def square(num):
    5. for i in range(1, num + 1):
    6. yield i * i # --1
    7. for i in square(5): # --2
    8. print(i, end = ' ')

    运行结果:

    1. > python yield_generator_test.py
    2. 1 4 9 16 25

    分析:上述代码第 1 处使用了 yield 关键字返回平方数,不再需要 return 关键字了。代码第 2 处调用函数 square() 返回的是生成器对象。生成器对象是一种可迭代对象,可迭代对象通过 __next__() 方法获得元素,代码第 2 处的 for 循环能够遍历可迭代对象,就是隐式地调用了生成器的 __next__() 方法获得元素的。

    显式地调用生成器的 __next__() 方法,在 Python Shell 中运行示例代码如下:

    1. >>> def square(num):
    2. ... for i in range(1, num + 1):
    3. ... yield i * i
    4. ...
    5. >>> n_seq = square(5)
    6. >>> n_seq.__next__() # --1
    7. 1
    8. >>> n_seq.__next__()
    9. 4
    10. >>> n_seq.__next__()
    11. 9
    12. >>> n_seq.__next__()
    13. 16
    14. >>> n_seq.__next__()
    15. 25
    16. >>> n_seq.__next__() # --2
    17. Traceback (most recent call last):
    18. File "", line 1, in
    19. StopIteration
    20. >>>

    分析:上述代码第 1 处和第 2 处共调用了 6 次 __next__() 方法,但第 6 次调用会抛出 StopIteration 异常,这是因为已经没有元素可迭代了。

            生成器函数通过 yield 返回数据,与 return 不同的是,return 语句一次返回所有数据,函数调用结束;而 yield 语句只返回一个元素数据,函数调用不会结束,只是暂停,直到 __next__() 方法被调用,程序继续执行 yield 语句之后的语句代码。这个过程如下图 1 所示。

    图1  生成器函数执行过程

    二、函数的高级内容

    Python 的函数是 “一级公民”,因此函数本身是也是一个对象,函数既可用于赋值,也可用于其他函数的参数,还可作为其他函数的返回值。

    2.1 使用函数变量

            Python 的函数也是一种值:所有函数都是 function 对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。

            当把函数赋值给变量之后,接下来程序也可通过该变量来调用函数。例如如下代码:

    1. # coding=utf-8
    2. # 代码文件: 函数/function_var_test.py
    3. # 使用函数变量:将函数赋值给变量的用法
    4. def pow(base, exponent):
    5. result = 1
    6. for i in range(1, exponent + 1):
    7. result *= base
    8. return result
    9. # 将 pow 函数赋值给变量 my_fun,则 my_fun 可被当成 pow 函数使用
    10. my_fun = pow # --1
    11. print(my_fun(3, 4)) # 输出 81
    12. # 定义一个计算面积的函数
    13. def area(width, heigth):
    14. return (width * heigth)
    15. # 将 area 函数赋值给 my_fun,则 my_fun 可被当成 area 使用
    16. my_fun = area # --2
    17. print(area(3, 4)) # 输出 12

    运行结果:

    1. > python function_var_test.py
    2. 81
    3. 12

    分析:从上面代码可以看出,程序依次将 pow()、area() 函数赋值给了 my_fun 变量,接下来即可通过 my_fun 变量分别调用 pow()、area() 函数。

    通过对 my_fun 变量赋值不同的函数,可以让 my_fun 在不同的时间指向不同的函数,从而让程序更加灵活。由此可见,使用函数变量的好处是让程序更加灵活。

    2.2 使用函数作为函数形参

            有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定——这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。

            Python 支持像使用其他参数一样使用函数参数,例如如下程序。

    1. # coding=utf-8
    2. # 代码文件: 函数/function_param_test.py
    3. # 使用函数作为函数形参的用法
    4. # 定义函数类型的形参,其中 fn 是一个函数类型变量
    5. def map(data, fn):
    6. result = [] # 声明一个空的列表
    7. # 遍历 data 列表中的每个元素,并用 fn 函数对每个元素进行计算
    8. # 然后将计算结果作为新列表的元素
    9. for e in data:
    10. result.append(fn(e))
    11. return result
    12. # 定义一个计算平方的函数
    13. def square(n):
    14. return n * n
    15. # 定义一个计算立方的函数
    16. def cube(n):
    17. return n * n * n
    18. # 定义一个计算阶乘的函数
    19. def factorial(n):
    20. result = 1
    21. for i in range(2, n + 1):
    22. result *= i
    23. return result
    24. data = [3, 4, 9, 5, 8]
    25. print('原数据:', data)
    26. # 下面程序代码调用map()函数三次,每次调用时传入不同的函数
    27. print('计算列表元素的平方:')
    28. print(map(data, square))
    29. print('计算列表元素的立方:')
    30. print(map(data, cube))
    31. print('计算列表元素的阶乘:')
    32. print(map(data, factorial))
    33. print(type(map))

    运行结果:

    1. > python function_param_test.py
    2. 原数据: [3, 4, 9, 5, 8]
    3. 计算列表元素的平方:
    4. [9, 16, 81, 25, 64]
    5. 计算列表元素的立方:
    6. [27, 64, 729, 125, 512]
    7. 计算列表元素的阶乘:
    8. [6, 24, 362880, 120, 40320]
    9. <class 'function'>

    分析:从上面介绍不难看出,通过使用函数作为参数可以在调用函数时动态传入函数——实际上就可以动态改变被调用函数的部分代码。

    在程序最后添加如下一行:

    print(type(map))

    分析:从运行结果可以看到,map() 函数是 function 类型,即函数类型,它是以 function 类的形式定义的。

    2.3 使用函数作为返回值

    前面已经提到,Python 还支持使用函数作为其他函数的返回值。例如如下程序:

    1. # coding=utf-8
    2. # 代码文件: 函数/function_return_test.py
    3. # 使用函数作为返回值的用法
    4. def get_math_func(type):
    5. # 定义一个计算平方的局部函数(嵌套函数)
    6. def square(n): # --1
    7. return n * n
    8. # 定义一个计算立方的函数
    9. def cube(n): # --2
    10. return n * n * n
    11. # 定义一个计算阶乘的函数
    12. def factorial(n): # --3
    13. result = 1
    14. for i in range(2, n + 1):
    15. result *= i
    16. return result
    17. # 返回局部函数
    18. if type == "square":
    19. return square
    20. if type == "cube":
    21. return cube
    22. else:
    23. return factorial
    24. # 调用get_math_func()函数,程序返回一个嵌套函数
    25. math_func = get_math_func("square") # 得到 square 函数
    26. print(math_func(5)) # 输出 25
    27. math_func = get_math_func("cube") # 得到 cube 函数
    28. print(math_func(5)) # 输出 125
    29. math_func = get_math_func("factorial") # 得到 factorial 函数
    30. print(math_func(5)) # 输出 120
    31. print(type(math_func)) # 输出

    运行结果:

    1. > python function_return_test.py
    2. 25
    3. 125
    4. 120
    5. <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() 函数将返回三个局部函数之一。该函数代码如下:

    1. def get_math_func(type):
    2. # 定义三个局部函数
    3. ...
    4. # 返回局部函数
    5. if type == "square":
    6. return square
    7. if type == "cube":
    8. return cube
    9. else:
    10. return factorial

            由于局部函数的作用域仅限于其封闭函数之内,因此这三个局部函数的函数名的作用太有限了——仅仅是在程序的 if 语句中作为返回值使用。一旦离开了 get_math_func() 函数体,这三个局部函数的函数名就失去了意义。

            既然局部函数的函数名没有太大的意义,那么就考虑使用 lambda 表达式来简化局部函数的写法,这就是 lambda 表达式引入的目的。

    3.2 使用 lambda 表达式代替局部函数

    如果使用 lambda 表达式来简化 function_return_test.py 程序,则可以将程序改写如下形式:

    1. # coding=utf-8
    2. # 代码文件: 函数/lambda_test.py
    3. # 使用 lambda 表达式代替局部函数的用法
    4. def get_math_func(type):
    5. # 该函数返回的是 lambda 表达式
    6. if type == "square":
    7. return lambda n: n * n # --1
    8. if type == "cube":
    9. return lambda n: n * n * n # --2
    10. else:
    11. return lambda n: (1 + n) * n / 2 # --3
    12. # 调用get_math_func()函数,程序返回一个嵌套函数
    13. math_func = get_math_func("square") # 得到 square 函数
    14. print(math_func(5)) # 输出 25
    15. math_func = get_math_func("cube") # 得到 cube 函数
    16. print(math_func(5)) # 输出 125
    17. math_func = get_math_func("factorial") # 得到 factorial 函数
    18. print(math_func(5)) # 输出 15.0
    19. print(type(math_func)) # 输出

    运行结果:

    1. > python lambda_test.py
    2. 25
    3. 125
    4. 15.0
    5. <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() 函数。

    1. # coding=utf-8
    2. # 代码文件: 函数/lambda_map.py
    3. # 使用 lambda 表达式来调用Python内置的map()函数
    4. # 传入计算平方的 lambda 表达式作为函数参数
    5. x = map(lambda x: x * x, range(8))
    6. print([i for i in x]) #输出 [0, 1, 4, 9, 16, 25, 36, 49]
    7. # 传入计算平方的 lambda 表达式作为参数
    8. y = map(lambda x: x * x if x % 2 == 0 else 0, range(8))
    9. print([i for i in y]) # 输出 [0, 0, 4, 0, 16, 0, 36, 0]

    运行结果:

    1. > python lambda_map.py
    2. [0, 1, 4, 9, 16, 25, 36, 49]
    3. [0, 0, 4, 0, 16, 0, 36, 0]

    分析:正如从上面代码所看到的,内置的 map() 函数的第一个参数需要传入函数,此处传入了函数的简化形式:lambda 表达式,这样程序更加简洁,而且性能更好。

    四、总结

            函数 和 lambda 表达式是 Python 编程的两大核心机制之一。Python 语言既支持面向过程编程,也支持面向对象编程。而函数 和 lambda 表达式就是 Python 面向过程编程的语法基础。

            在学习 Python 函数式编程过程中,不仅需要掌握函数定义、函数定义的语法,还需要掌握函数位置参数、关键字参数的区别和用法、形参默认值等高级特性。除此之外,函数也是一个 function 对象,因此函数既可作为其他函数的参数,也可作为其他函数的返回值,通过把函数当成参数传入其他函数,可以让编程变得更加灵活。

            Python 的 lambda 表达式只是单行函数的简化版本,因此 lambda 表达式的功能比较简单。

    五、编程题

    1、定义一个函数,该函数可接收一个 list 作为参数,该函数使用直接选择排序对 list 排序。

    1. # coding=utf-8
    2. # 直接选择排序
    3. def direct_select_sort(list):
    4. list_len = len(list)
    5. for i in range(0, list_len):
    6. for j in range(i + 1, list_len):
    7. if list[i] > list[j]:
    8. list[i], list[j] = list[j], list[i]
    9. s = input("输入待排序数:")
    10. list1 = s.split() # 按空格拆分字符串s
    11. list1 = [int(list1[i]) for i in range(len(list1))] # 将列表每个字符串类型元素转换成int类型
    12. print("排序前:", list1)
    13. direct_select_sort(list1)
    14. print("排序后:", list1)

    示例运行结果:

    1. 输入待排序数:3 6 1 8 5 -20 100 50 200
    2. 排序前: [3, 6, 1, 8, 5, -20, 100, 50, 200]
    3. 排序后: [-20, 1, 3, 5, 6, 8, 50, 100, 200]

    2、定义一个函数,该函数可接收一个 list 作为参数,该函数使用冒泡排序对 list 排序。

    1. # coding=utf-8
    2. # 冒泡排序
    3. def bubble_sort(list):
    4. list_len = len(list)
    5. for i in range(0, list_len):
    6. is_sorted = True
    7. for j in range(0, list_len - 1 - i):
    8. if list[j] > list[j + 1]:
    9. list[j], list[j + 1] = list[j + 1], list[j]
    10. is_sorted = False
    11. if is_sorted: return # 如果是已排好序的,则直接return
    12. s = input("输入待排序数:")
    13. list1 = s.split() # 按空格拆分字符串s
    14. list1 = [int(list1[i]) for i in range(len(list1))] # 将列表每个字符串类型元素转换成int类型
    15. print("排序前:", list1)
    16. bubble_sort(list1)
    17. print("排序后:", list1)

    示例运行结果:

    1. > python test.py
    2. 输入待排序数:3 6 1 8 5 -20 100 50 200
    3. 排序前: [3, 6, 1, 8, 5, -20, 100, 50, 200]
    4. 排序后: [-20, 1, 3, 5, 6, 8, 50, 100, 200]

    3、定义一个 is_leap(year) 函数,该函数判断 year 是否为闰年。若是闰年,则返回 True;否则返回 False。

    1. # coding=utf-8
    2. import sys
    3. # 判断闰年
    4. def is_leap(year):
    5. year = int(year)
    6. if (year % 4 == 0) and (year % 100 != 0):
    7. return True
    8. elif year % 400 == 0:
    9. return True
    10. else:
    11. return False
    12. while True:
    13. year = input("请输入一个年份:")
    14. if (year == 'exit') or (year == 'quit'):
    15. sys.exit(0)
    16. print("%s是闰年吗? %s" %(year, is_leap(year)))

    示例运行结果:

    1. > python test.py
    2. 请输入一个年份:2022
    3. 2022是闰年吗? False
    4. 请输入一个年份:2020
    5. 2020是闰年吗? True
    6. 请输入一个年份:quit

    4、定义一个 count_str_char(my_str) 函数,该函数返回参数字符串中包含有多少个数字、多少个英文字母、多少个空白字符、多少个其他字符。

    1. # coding=utf-8
    2. import sys
    3. def count_str_char(my_str):
    4. if my_str == None: return 0, 0, 0, 0
    5. digit_num, char_num, space_num, others_num = 0, 0, 0, 0
    6. for c in my_str:
    7. if c.isdigit(): digit_num += 1
    8. elif c.isalpha(): char_num += 1
    9. elif c.isspace(): space_num += 1
    10. else: others_num += 1
    11. return digit_num, char_num, space_num, others_num
    12. while True:
    13. string = input("请输入一个字符串: ")
    14. if (string == 'exit') or (string == 'q'):
    15. sys.exit(0)
    16. digit_num, char_num, space_num, others_num = count_str_char(string)
    17. print('数字个数:', digit_num)
    18. print('字母个数:', char_num)
    19. print('空格个数:', space_num)
    20. print('其他字母个数:', others_num)

    示例运行结果:

    1. > python test.py
    2. 请输入一个字符串: Copyright (C), 2001-2018, yeeku.H.Lee
    3. 数字个数: 8
    4. 字母个数: 19
    5. 空格个数: 3
    6. 其他字母个数: 7
    7. 请输入一个字符串: q

    5、定义一个 fn(n) 函数,该函数返回 1~n 的立方和,即求 1^{3}+2^{3}+3^{3}+...+n^{3}

    1. # coding=utf-8
    2. def fn(n):
    3. if n < 1:
    4. print("输入错误!")
    5. exit()
    6. sum = 0
    7. for i in range(1, n + 1):
    8. sum += i ** 3
    9. return sum
    10. n = int(input("请输入一个整数:"))
    11. result = fn(n)
    12. print("1~%d的立方和是:%d" %(n, result))

    6、定义一个 fn(n) 函数,该函数返回 n 的阶乘。

    1. # coding=utf-8
    2. def fn(n):
    3. if n < 1:
    4. print("输入错误!")
    5. exit()
    6. if n == 1:
    7. return 1
    8. else:
    9. return n * fn(n-1) # 函数递归调用
    10. n = int(input("请输入一个整数:"))
    11. result = fn(n)
    12. print("%d的阶乘是:%d" %(n, result))

    7、定义一个函数,该函数可接收一个 list 作为参数,该函数用于去除 list 中重复的元素。

    假设:list 列表中的元素是 int 类型的,实现代码如下:

    1. # coding=utf-8
    2. def remove_duplicate(list):
    3. if list == None: return None
    4. new_list = []
    5. [new_list.append(list[i]) for i in range(len(list)) if list[i] not in new_list] # 列表推导式
    6. return new_list
    7. string = input("请输入列表元素(用空格隔开):")
    8. my_list = string.split() # 按空格拆分字符串string
    9. my_list = [int(my_list[i]) for i in range(len(my_list))] # 将列表每个字符串类型元素转换成int类型
    10. print(remove_duplicate(my_list))

    示例运行结果:

    1. > python test.py
    2. 请输入列表元素(用空格隔开):15 12 13 15 12 16
    3. [15, 12, 13, 16]

    8、定义一个 fn(n) 函数,该函数返回一个包含 n 个不重复的 0~100 之间整数的元组。

    1. # coding=utf-8
    2. import random
    3. def fn(n):
    4. i, tmp_list = 0, []
    5. while True:
    6. num = random.randint(0, 100)
    7. # 如果随机数不包含在列表中,则保存
    8. if num not in tmp_list:
    9. tmp_list.append(num)
    10. i += 1
    11. if i == n:
    12. break
    13. # 将列表转出元组返回
    14. return tuple(tmp_list)
    15. n = int(input("请输入整数n:"))
    16. print(fn(n))

    9、定义一个 fn(n) 函数,该函数返回一个包含 n 个不重复的大写字母的元组。

    1. # coding=utf-8
    2. import random
    3. def fn(n):
    4. i, tmp_list = 0, [] # 初始化变量和列表
    5. while True:
    6. # 大写字母A的ASCII值为65,大写字母Z的ASCII值为90
    7. num = random.randint(65, 65 + 25)
    8. # 如果随机数不在列表中,则保存
    9. ch = chr(num) # 将整数转换成ASCII字符类型
    10. if ch not in tmp_list:
    11. tmp_list.append(ch)
    12. i += 1
    13. if i == n:
    14. break
    15. # 将列表转换成元组返回
    16. return tuple(tmp_list)
    17. n = int(input("请输入整数n:"))
    18. print(fn(n))

    10、定义一个 fn(n) 函数,其中 n 表示输入 n 行 n 列的矩阵(数的方阵)。在输出时,先输出 n 行 n 列的矩阵,再输出该矩阵的转置矩阵。例如,当参数输入 3 时,先输出:

    1. 1 2 3
    2. 4 5 6
    3. 7 8 9

    再输出:

    1. 1 4 7
    2. 2 5 8
    3. 3 6 9
    1. # coding=utf-8
    2. import random
    3. def fn(n):
    4. # 输出初始矩阵
    5. for i in range(n):
    6. for j in range(n):
    7. print('%4d ' %(i * n + j + 1), end = '')
    8. print()
    9. print('-' * (5 * n)) # 打印一行分隔符
    10. # 输出转置矩阵
    11. for i in range(n):
    12. for j in range(n):
    13. print('%4d ' %(j * n + i + 1), end = '')
    14. print()
    15. n = int(input("请输入整数n:"))
    16. print(fn(n))

    示例运行结果:

    1. > python test.py
    2. 请输入整数n:3
    3. 1 2 3
    4. 4 5 6
    5. 7 8 9
    6. ---------------
    7. 1 4 7
    8. 2 5 8
    9. 3 6 9
    10. None
    11. > python test.py
    12. 请输入整数n:4
    13. 1 2 3 4
    14. 5 6 7 8
    15. 9 10 11 12
    16. 13 14 15 16
    17. --------------------
    18. 1 5 9 13
    19. 2 6 10 14
    20. 3 7 11 15
    21. 4 8 12 16
    22. None
    23. > python test.py
    24. 请输入整数n:5
    25. 1 2 3 4 5
    26. 6 7 8 9 10
    27. 11 12 13 14 15
    28. 16 17 18 19 20
    29. 21 22 23 24 25
    30. -------------------------
    31. 1 6 11 16 21
    32. 2 7 12 17 22
    33. 3 8 13 18 23
    34. 4 9 14 19 24
    35. 5 10 15 20 25
    36. None

    参考

    《Python从小白到大牛(第1版-2018).pdf》第10章 - 函数式编程

    《疯狂Python讲义(2018.12).pdf》第5章 - 函数和lambda表达式       

    《Python编程:从入门到实践(2016.7).pdf》第8章 - 函数

  • 相关阅读:
    灵界的科学丨五、心灵与意识的科学奥祕
    基于java(ssm)人事考勤签到管理系统源码(java毕业设计)
    计算机毕业设计springboot+vue+elementUI农机电招租赁预约平台
    导出数据库表信息生成Word文档
    程序员短视频上瘾综合症
    怎么将摄像头视频以及坐标传到上位机上
    如何建立完整的、有效的会员体系?
    MySQL面试知识点总结(持续更新)
    基于无人机的物联网空基中继鲁棒优化
    《进化优化》第1章 绪论
  • 原文地址:https://blog.csdn.net/u010429831/article/details/126187690