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

一、生成器

        在一个函数中经常使用 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 所示。

图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 的立方和,即求 1^{3}+2^{3}+3^{3}+...+n^{3}

# 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章 - 函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值