更好的阅读体验:点这里 ( www.foooor.com
)
10 函数式编程
什么是函数式编程?
就是用函数来解决问题,函数可以赋值给变量、函数可以作为参数传递给另外一个函数、函数可以作为返回值。
10.1 函数作为参数
1 函数赋值给变量
函数可以赋值给变量,看一下下面的代码:
def compute(x, y): # 定义一个函数
return x + y
func = compute # 将函数赋值给变量,注意函数名称后面没有括号
result = func(1, 2) # 通过变量调用函数
print(result)
将函数赋值给变量,注意函数后面没有括号,有括号就是调用函数了。
函数赋值给变量后,可以通过变量调用函数。
2 函数作为参数
函数可以作为参数传递给另外一个函数
def plus(x, y): # 定义一个加法的函数
return x + y
def multiply(x, y): # 定义一个乘法的函数
return x * y
def test_func(x, y, func): # 第三个参数是一个函数,在该函数中调用了这个函数
return func(x, y)
result1 = test_func(1, 2, plus) # 传递三个参数,第三个参数是一个函数
print(result1)
result2 = test_func(1, 2, multiply)
print(result2)
在上面的代码中,我们将两个数字和一个函数作为参数传递给了 test_func
函数,在 test_func
函数中,将两个数字作为参数,调用了作为参数的函数。
这样根据传入的函数的不同,实现了不同的逻辑,这是计算逻辑的传递,而不是数据的传递。
这样做有什么好处呢?
3 函数作为参数的应用
我们现在有一个学生列表:
class Student:
def __init__(self, name, chinese, math, english):
self.name = name
self.chinese = chinese
self.math = math
self.english = english
def __str__(self):
return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"
# 学生列表
all_stu_list = [Student("zhangsan", 87, 48, 92),
Student("lisi", 47, 92, 71),
Student("wangwu", 58, 46, 38),
Student("zhangliu", 95, 91, 99)]
现在想获取语文成绩不及格的同学、所有课程不及格的同学、所有课程都是优秀的学生,那么我们需要定义三个函数,如下:
# 获取语文成绩不及格的同学
def get_chinese_flunk_list():
stu_list = []
for stu in all_stu_list:
if stu.chinese < 60:
stu_list.append(stu)
return stu_list
# 获取所有课程不及格的同学
def get_flunk_list():
stu_list = []
for stu in all_stu_list:
if stu.chinese < 60 and stu.math < 60 and stu.english < 60:
stu_list.append(stu)
return stu_list
# 获取所有课程都是优秀的学生
def get_excellent_list():
stu_list = []
for stu in all_stu_list:
if stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90:
stu_list.append(stu)
return stu_list
# -------------方法调用---------------------
print("语文不及格的同学:")
stu_list = get_chinese_flunk_list()
for stu in stu_list:
print(stu)
print("所有不及格的同学:")
stu_list = get_flunk_list()
for stu in stu_list:
print(stu)
print("所有成绩都是优秀的同学:")
stu_list = get_excellent_list()
for stu in stu_list:
print(stu)
功能是实现了,但是上面的代码有一个问题,就是代码复用性差。上面定义了3个函数 get_chinese_flunk_list
get_flunk_list
, get_excellent_list
,只有其中的判断条件不同。 能否抽出判断条件,将判断条件作为参数传递呢?
我们先抽出一个公共的,根据条件获取学生列表的函数:
# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
stu_list = []
for stu in all_stu_list:
if fun_condition(stu):
stu_list.append(stu)
return stu_list
上面的参数传进来的是一个函数,在 if 的判断后面执行这个函数,如果这个函数返回的是true,则将元素添加到列表中。
然后我们再编写三个获取筛选学生条件的函数,根据传进来的参数进行判断:
# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
return stu.chinese < 60
# 获取所有课程不及格的同学
def get_flunk_condition(stu):
return stu.chinese < 60 and stu.math < 60 and stu.english < 60
# 获取所有课程都是优秀的学生
def get_excellent_condition(stu):
return stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90
然后我们可以根据条件获取相应的学生了,都是调用共用的函数 get_stu_list_by_condition
, 传入不同的条件获取不同的学生列表:
print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for stu in stu_list:
print(stu)
print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(get_flunk_condition)
for stu in stu_list:
print(stu)
print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(get_excellent_condition)
for stu in stu_list:
print(stu)
这样已函数作为参数进行逻辑的控制,代码更为优雅,复用性更高。
完整代码:
class Student:
def __init__(self, name, chinese, math, english):
self.name = name
self.chinese = chinese
self.math = math
self.english = english
def __str__(self):
return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"
all_stu_list = [Student("zhangsan", 87, 48, 92),
Student("lisi", 47, 92, 71),
Student("wangwu", 58, 46, 38),
Student("zhangliu", 95, 91, 99)]
# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
stu_list = []
for stu in all_stu_list:
if fun_condition(stu):
stu_list.append(stu)
return stu_list
# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
return stu.chinese < 60
# 获取所有课程不及格的同学
def get_flunk_condition(stu):
return stu.chinese < 60 and stu.math < 60 and stu.english < 60
# 获取所有课程都是优秀的学生
def get_excellent_condition(stu):
return stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90
print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for item in stu_list:
print(item)
print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(get_flunk_condition)
for item in stu_list:
print(item)
print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(get_excellent_condition)
for item in stu_list:
print(item)
执行结果:
语文不及格的同学:
name:lisi, chinese:47, math:92, english:71
name:wangwu, chinese:58, math:46, english:38
所有不及格的同学:
name:wangwu, chinese:58, math:46, english:38
所有成绩都是优秀的同学:
name:zhangliu, chinese:95, math:91, english:99
4 lambda表达式
上面作为参数传递的函数,使用lambda表达式来写,可以更简洁。
lambda表达式的语法:
lambda 参数: 方法体
方法体不需要return语句,会自动返回。
以获取语文成绩不及格的条件函数为例,我们可以将函数 get_chinese_flunk_condition
改写成 lambda表达式。
# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
return stu.chinese < 60
改写为:
lambda stu: stu.chinese < 60
将另外两个函数也改成 lambda 表达式,在调用 get_stu_list_by_condition
函数的时候,就不用传入函数了,省去了3个获取条件的函数:
print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60)
for item in stu_list:
print(item)
print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60 and stu.math < 60 and stu.english < 60)
for item in stu_list:
print(item)
print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90)
for item in stu_list:
print(item)
代码是不是更简洁了。
lambda就是为了将方法作为参数而存在的,当然如果你将lambda表达式赋值给一个变量,也不是不可以,只是有点没事找抽,那还不如定义成原来函数的模样。
get_chinese_flunk_condition = lambda stu: stu.chinese < 60
print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for item in stu_list:
print(item)
需要注意:lambda表达式的方法体只能有一句代码,无法写多行。
lambda表达式相对于传统的函数,它是随时创建随时销毁的,不会保存在方法区中。
完整代码:
class Student:
def __init__(self, name, chinese, math, english):
self.name = name
self.chinese = chinese
self.math = math
self.english = english
def __str__(self):
return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"
all_stu_list = [Student("zhangsan", 87, 48, 92),
Student("lisi", 47, 92, 71),
Student("wangwu", 58, 46, 38),
Student("zhangliu", 95, 91, 99)]
# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
stu_list = []
for stu in all_stu_list:
if fun_condition(stu):
stu_list.append(stu)
return stu_list
print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60)
for item in stu_list:
print(item)
print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60 and stu.math < 60 and stu.english < 60)
for item in stu_list:
print(item)
print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90)
for item in stu_list:
print(item)
5 lambda表达式的常见写法
无参函数改写为lambda
def func():
return 100
# 改写为:
lambda : 100
多参数函数改写为lambda
def func(x, y):
return x + y
# 改写为:
lambda x, y: x + y
没有返回值的函数
lambda表示也是可以没有返回值的。
def func(x):
print(x)
# 改写为:
lambda x: print(x)
6 内置高阶函数
内置高阶函数也就是Python内置的可接受函数的方法。
map函数
map函数接收两个参数:一个函数和一个可迭代对象。map函数将传递给它的函数应用于可迭代对象的每个元素,返回一个新的可迭代对象,其中每个元素都是执行传递的函数后的结果。
举个栗子:
# 将列表中的每个元素乘以2
my_list = [1, 2, 3, 4, 5]
result = map(lambda x: x*2, my_list)
print(list(result)) # [2, 4, 6, 8, 10]
在这个例子中,我们将一个lambda函数应用于my_list中的每个元素,将每个元素乘以2。我们使用list()函数将结果打印出来。
需要注意,map返回的结果不是一个list,而是一个类似于迭代器的可迭代对象,可以使用 list() 函数转换为列表
filter函数
filter函数接收两个参数:一个函数和一个可迭代对象。filter函数将传递给它的函数应用于可迭代对象的每个元素,并返回一个新的可迭代对象,其中包含使传递的函数返回True的元素。
举个栗子:
# 过滤掉列表中的奇数
my_list = [1, 2, 3, 4, 5]
result = filter(lambda x: x % 2 == 0, my_list)
print(list(result)) # [2, 4]
在这个例子中,我们使用lambda函数筛选出my_list中的偶数。我们使用list()函数将结果打印出来。
sorted函数
sorted函数接收一个可迭代对象和两个可选参数:key和reverse。sorted函数将传递给它的可迭代对象进行排序,并返回一个新的已排序的列表。如果指定了key参数,则将其用作排序关键字。如果指定了reverse参数,则将其用作布尔值,以指定是否应按相反的顺序对列表进行排序。
举个栗子:
# 对列表进行排序
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = sorted(my_list)
print(result) # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
# 按字符串长度对列表进行排序
my_list = ["apple", "banana", "pear", "orange"]
result = sorted(my_list, key=len)
print(result) # ['pear', 'apple', 'banana', 'orange']
在第一个例子中,我们对my_list进行排序,以便我们可以使用sorted函数返回已排序的列表。在第二个例子中,我们使用key参数按字符串长度对my_list进行排序。我们使用print()函数将结果打印出来。
max函数
max函数接收一个可迭代对象,并返回其中的最大值。
举个栗子:
# 获取列表中的最大值
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = max(my_list)
print(result) # 9
在这个例子中,我们使用max函数返回my_list中的最大值。我们使用print()函数将结果打印出来。
min函数
min函数收一个可迭代对象,并返回其中的最小值。
举个栗子:
# 获取列表中的最小值
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = min(my_list)
print(result) # 1
在这个例子中,我们使用min函数返回my_list中的最小值。我们使用print()函数将结果打印出来。
10.2 外部嵌套作用域
以前已经讲过了关于作用域的一些问题,使用 global 关键字,在函数内部修改全局的变量。
下面来讲解一下外部嵌套作用域。
1 外部嵌套作用域
什么是外部嵌套作用域?
外部嵌套作用域就是允许在一个函数内部定义另一个函数,并让内部函数访问外部函数的变量。
举个栗子:
我们在一个函数内部定义了另外一个函数,并调用了这个内部函数。
def outer_func():
a = 1
def inner_func():
print(a)
inner_func() # 调用内部函数
outer_func() # 调用外部函数
执行结果:
1
现在我们尝试在内部函数中修改外部函数中定义的变量:
def outer_func():
a = 1
def inner_func():
a = 2 # 并不能修改外部函数的变量,这里是在内部函数中又创建了和外部函数同名的变量
print(a)
inner_func() # 调用内部函数
print(a)
outer_func() # 调用外部函数
执行结果:
2
1
可以看到在内部函数中并没能修改外部函数中的变量。
2 nonlocal
我们可以使用 nonlocal 关键字,在内部函数中修改外部函数中的变量。
举个栗子:
def outer_func():
a = 1
def inner_func():
nonlocal a # 声明外部嵌套作用域
a = 2
print(a)
inner_func() # 调用内部函数
print(a)
outer_func() # 调用外部函数
执行结果:
2
2
总结:
- 外部嵌套作用域允许内部函数访问外部函数的变量。
- 内部函数可以访问外部函数的变量,但是不能对其进行修改。
- 如果需要修改外部函数中的变量,可以使用 Python 中的
nonlocal
关键字。
10.3 函数作为返回值
1 闭包
什么是闭包?
闭包必须满足三个条件:
- 在一个外部函数中有一个内部函数
- 内部函数必须引用外部函数中的变量
- 外部函数的返回值必须是内部函数
语法:
def 外部函数名(参数):
外部变量
def 内部函数名(参数):
使用外部函数变量
return 内部函数名
举个栗子:
def outer_func():
a = 1
def inner_func():
print(a) # 使用了外部函数的变量
return inner_func; # 将内部函数返回,这里函数名后没有括号
result = outer_func() # 这里得到的结果是一个函数
result() # 执行内部函数,结果为:1
闭包代码运行的内存图
首先代码自上而下执行,遇到函数 outer_func(),会将函数加载到方法区,并不会执行函数。
然后执行代码:result = outer_func()
, 创建 outer_func
函数的栈帧,执行函数 outer_func()
,执行 a = 1
,然后定义了函数 inner_func()
,则将函数 inner_func()
存储到方法区,此时并没有执行函数 inner_func()
,然后将函数inner_func()
返回,相当于创建了一个变量 inner_func
指向了方法取中的函数 inner_func()
执行完函数 inner_func
,将结果赋值给了变量result,然后执行result,相当于执行了函数 outer_func
,则开辟了函数 outer_func
的栈帧。
由于闭包是将内部函数返回了,所以外部函数执行完毕后,栈帧并不会立即销毁,而是等待内部函数执行。
上图蓝色的区域的环境也就是闭包的环境。
下面来讲解一下闭包的实际应用。
2 装饰器
什么是装饰器?
装饰器其实就是闭包的一种应用, 其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。
例如,我现在有一个洗澡的函数:
def bath():
print("我在洗澡...")
bath()
但是我在洗澡之前需要脱衣服,在洗澡之后需要穿衣服,于是我修改代码:
def bath():
print("脱衣服")
print("我在洗澡...")
print("穿衣服")
bath()
这样就需要修改洗澡的方法,我能否不修改洗澡的方法,在洗澡前后做一些事情呢?
可以使用装饰器写法:
def bath_wrapper(func): # 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
def wrapper():
print("脱衣服")
func()
print("穿衣服")
return wrapper;
def bath(): # 洗澡方法不变
print("我在洗澡...")
func = bath_wrapper(bath) # 返回的是一个经过包装的函数
func() # 执行包装的函数
洗澡的函数不变,我们重新定义了一个函数,在函数内部定义了内部函数,对要执行的函数 func()
进行了包装,在函数前后做了一些事情。
上面的写法仍然有缺点,就是我调用的时候,需要调用包装的函数,得到一个新函数再执行,还可以再进行优化:
def bath_wrapper(func): # 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
def wrapper():
print("脱衣服")
func()
print("穿衣服")
return wrapper;
@bath_wrapper
def bath(): # 洗澡方法不变
print("我在洗澡...")
# 直接执行洗澡的函数,会自动执行包装函数
bath()
我们通过注解的方式,将包装的函数添加到了 bath
函数上,这样可以直接调用原来的函数,便会执行包装函数中封装的内容。
执行结果:
脱衣服
我在洗澡…
穿衣服
但是现在仍然有一个问题,就是传参问题,尤其是当我还有一个睡觉的方法,我在睡觉前后也要脱衣服和穿衣服,而且洗澡和睡觉的参数不一样。下面的代码就会报错,因为包装函数中没有处理参数:
def bath_wrapper(func): # 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
def wrapper():
print("脱衣服")
func()
print("穿衣服")
return wrapper;
@bath_wrapper
def bath(singer, song):
print(f"我在洗澡, 唱一首{singer}的{song}")
@bath_wrapper
def sleep(duration):
print(f"我在睡觉, 睡{duration}小时")
# 直接执行洗澡的函数,会自动执行包装函数
bath("偶像", "鸡你太美")
sleep(8)
需要修改代码如下:
def bath_wrapper(func): # 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
def wrapper(*args, **kwargs): # 接受各种传参方式
print("脱衣服")
func(*args, **kwargs) # 将参数拆开传递
print("穿衣服")
return wrapper;
@bath_wrapper
def bath(singer, song):
print(f"我在洗澡, 唱一首{singer}的{song}")
@bath_wrapper
def sleep(duration):
print(f"我在睡觉, 睡{duration}小时")
# 直接执行洗澡的函数,会自动执行包装函数
bath("偶像", "鸡你太美")
sleep(8)
函数的传参问题,可以见 函数 --> 函数的参数 章节。
执行结果:
脱衣服
我在洗澡, 唱一首偶像的鸡你太美
穿衣服
脱衣服
我在睡觉, 睡8小时
穿衣服
3 在函数之间共享数据
使用闭包可以在内部函数和外部函数之间共享数据。
举个栗子:
def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
c1 = counter() # 创建了一个计数器
print(c1()) # 输出 1
print(c1()) # 输出 2
c2 = counter() # 创建了第二个计数器
print(c2()) # 输出 1
在上面的代码中,创建了闭包,用来实现了一个计数器的功能。
当我们调用外部函数 counter
就会创建一个计数器,当我们调用这个计数器的时候,count
的值就会加1,由于我们每次调用counter
函数时都会创建一个新的闭包,因此我们可以创建多个计数器,每个计数器都是独立的。
执行结果:
1
2
1
其实,我们也可以创建一个计数器的类,在类中定义一个count属性,也可以实现上面的功能,例如:
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
# 创建计数器
counter1 = Counter()
counter2 = Counter()
print(counter1.increment()) # 输出 1
print(counter2.increment()) # 输出 1
一般情况下,类实现方式更适合需要维护的数据结构较为复杂的情况,比如需要使用多个属性和方法来处理数据。而闭包实现方式更适合只需要共享一些简单数据的情况。
4 创建私有变量
我们之前讲过类的私有变量,但其实那只是骗人的障眼法,也是可以调用和修改的。但是我们使用闭包可以实现私有变量。
举个栗子:
下面的代码并没有使用类,而是使用了函数实现了类的功能。
在下面的代码中,我们定义了一个函数 create_person
,可以通过它来返回对象,只是这个对象是字典的形式。
在类中定义age变量。
def create_person(name):
age = 0
def get_age():
nonlocal age
return age
def set_age(new_age):
nonlocal age
if new_age >= 0:
age = new_age
else:
print("Invalid age")
def get_name():
return name
return {
'get_name': get_name,
'get_age': get_age,
'set_age': set_age
}
person = create_person("逗比")
print(person['get_name']()) # 输出 Alice
print(person['get_age']()) # 输出 0
person['set_age'](30)
print(person['get_age']()) # 输出 30
通过上面的代码可以看出,我们是无法直接访问和修改 age
变量的,必须通过 get_age
和 set_age
内部函数才可以,因此它对外部代码是不可见的,可以被看作是一个私有变量。
而 name
只能通过 get_name
函数来获取,也是无法修改的。
同时和类很相似,我们调用一次 create_person
函数,就会创建一个对象。
5 作为回调函数
回调函数指的是将一个函数作为参数传递给另外一个函数,然后在这个函数中执行这个参数函数。回调函数通常用于异步编程中,可以在事件发生后回调执行,以完成一些特定的任务。例如:在实际的功能实现中,我们经常会做一些耗时的操作,例如网络请求,发起网络请求后,继续执行后面的代码,待服务器返回结果后,在通过回调函数的方式返回结果。
这里举一个回调函数的列子:
def plus(a, b, callback): # callback参数就是一个函数
result = a + b
callback(result) # 执行callback函数将结果返回
def print_result(result):
print("The result is:", result)
plus(10, 20, print_result) # 输出 The result is: 30
调用 plus
函数的时候,传入 print_result
函数,得到计算结果后,调用 print_result
函数,并传递结果。