前言:函数式编程有什么用,为什么要学函数式编程?函数式编程是一种编程范式,可减少程序的错误和复杂度,提高代码的可维护性和可重用性。
1.定义:用一系列函数解决问题
? ? ? ? 函数可以赋值给变量,赋值后变量绑定函数。
? ? ? ? 允许将函数作为参数传入另一个函数。
2.高阶函数:将函数作为参数或返回值的函数
函数作为参数
将核心逻辑传入方法体,使该方法的适用性更广,体现了面向对象的开闭原则。
那什么是开闭原则呢?
对扩展开放,对修改封闭。
当软件系统需要进行功能扩展时,应该可以通过增加新的代码来实现,而不是修改已有的代码。开闭原则的核心思想是面向接口编程,通过抽象和封装来实现代码的可扩展性和可维护性。开闭原则和前面所提到的先确定用法后决定做法如出一辙,用法不能修改,但是能新加做法。
假如现在有两个需求:
1.定义函数,在列表中查找第一个奇数
2.定义函数,在列表中查找第一个能被3或5整除的数字
不使用函数式编程思想来实现:
# 不使用函数式编程思想
# 1.定义函数,在列表中查找第一个奇数
def find_first_odd(numbers):
for number in numbers:
if number % 2 != 0:
return number
# 2.定义函数,在列表中查找第一个能被3或5整除的数字
def find_first_divisible(numbers):
for number in numbers:
if number % 3 == 0 or number % 5 == 0:
return number
if __name__ == '__main__':
list_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(find_first_odd(list_num)) # 1
print(find_first_divisible(list_num)) # 3
上述代码当然能实现我们提到的两个需求,可当我们的功能增加,查找列表中第一个偶数,我们则有需要定义一个函数。可我们发现其实这些需求的主体逻辑相同,核心算法不同,这个时候我们就可以抽象主题逻辑创建一个适用性更广的函数,再将核心算法通过参数传递给主题逻辑函数,而这个被抽象化的主题逻辑函数被称为高阶函数。高阶函数是早就写好的不能修改,是闭,而核心算法是当下决定实现的扩展功能,属于开,这也体现了面向对象的开闭原则。
使用函数式编程的思想实现:
# 使用函数式编程思想
# 抽象相同的主题结构,创建一个高阶函数
def find_single(iterable, condition):
for item in iterable:
if condition(item):
return item
def find_first_odd(number):
return number % 2 == 1
def find_first_divisible(number):
return number % 3 == 0 or number % 5 == 0
if __name__ == '__main__':
list_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(find_single(list_num, find_first_odd)) # 1
print(find_single(list_num, find_first_divisible)) # 3
用为主题逻辑相同,核心算法不同
所以使用函数式编程思想(分,隔,做)
分:将主题逻辑和核心算法分开
隔:将核心算法使用形参代替
做:将想要实现的需求的核心算法做出来
上述的代码中的核心算法非常简单,但是还需要给它定义函数名,显得非常的麻烦,应该如何解决呢?lambda 表达式,匿名方法
lambda表达式
定义:是一种匿名方法
作用:
作为参数传递时语法简洁,优雅,代码可读性强。
随时创建和销毁,减少程序耦合度。
语法:
变量 = lambda 形参:方法体
调用:变量(实参)
限制:形参没有可以不填,方法体只能有一条语句,且不支持赋值语句。
有了lambda我们再来优化我们上面的代码吧:
# 使用函数式编程思想
# 抽象相同的主题结构,创建一个高阶函数
def find_single(iterable, condition):
for item in iterable:
if condition(item):
return item
if __name__ == '__main__':
list_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(find_single(list_num, lambda x: x % 2 == 1)) # 1
print(find_single(list_num, lambda x: x % 3 == 0 or x % 5 == 0)) # 3
一般我们都将自定义的高阶函数封装起来:
from common.iterable_tools import IterableHelper
if __name__ == '__main__':
list_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(IterableHelper.find_single(list_num, lambda x: x % 2 == 1)) # 1
print(IterableHelper.find_single(list_num, lambda x: x % 3 == 0 or x % 5 == 0)) # 3
# common -> iterable_tools.py
"""
对可迭代对象的常用操作
"""
class IterableHelper:
"""
可迭代对象助手:封装对可迭代对象常用的高阶函数
集成操作框架
"""
@staticmethod # 静态方法:不让python解释器自动为第一个参数传递self
def find_single(iterable, condition):
"""
在可迭代对象中查找第一个满足条件的元素
:param iterable:可迭代对象
:param condition:函数类型,一个参数,一个bool返回值
:return:第一个满足条件的元素
"""
for item in iterable:
if condition(item):
return item
在工作中,或者在学习中我们自定义的通用工具都应该封装起来,提高工作效率。
内置高阶函数
map(函数,可迭代对象):使用可迭代对象中的每个元素调用函数,将返回值作为新可迭代对象元素;返回值为新可迭代对象。
filter(函数,可迭代对象):根据条件筛选可迭代对象中的元素,返回值为新可迭代对象。
sorted(可迭代对象,key = 函数,reverse = bool值):排序,返回值为排序结果。
max(可迭代对象,key = 函数):根据函数获取可迭代对象的最大值
min(可迭代对象,key = 函数):根据函数获取可迭代对象的最小值
大家要学会举一反三,我们刚才自己写了高阶函数,面对祖师爷给我们写的内置高阶函数我们也应该使用,并能模拟出能实现相同功能的高阶函数。
大家可以来写一些高阶函数:
my_map, my_filter, my_sorted, my_max, my_min
实现和对应内置高阶函数相同的功能。
函数作为返回值
逻辑连续,当内部函数被调用时,不脱离当前的逻辑。
闭包
三要素:外中有内,内使用外,外返回内。
代码案例:
# 闭包案例
def func01():
print("func01")
a = 10
def func02():
print("func02")
print(a)
return func02
if __name__ == '__main__':
condition = func01() # func01
condition() # func02 10
上面代码它包含了闭包三要素,它是一个闭包。
优点:内部函数可以使用外部变量
缺点:外部变量一直存在于内存中,不会在调用结束后释放,占用内存。
作用:实现python装饰器
装饰器原理:
# 装饰器
def new(func):
def wrapper(*args, **kwargs):
print('我是新功能')
res = func(*args, **kwargs)
return res
return wrapper
def old(a, b, c):
print('我是旧功能')
print(a, b, c)
return '执行旧功能成功'
old = new(old)
old()
上面代码,在不修改旧功能的前提下,在每次执行旧功能时都会先执行新功能。
上面是原理,在实际开发中我们一般写成这样
# 装饰器
def new(func):
def wrapper(*args, **kwargs):
print('我是新功能1')
res = func(*args, **kwargs)
return res
return wrapper
def new2(func):
def wrapper(*args, **kwargs):
print('我是新功能2')
res = func(*args, **kwargs)
return res
return wrapper
@new2
@new
def old(a, b, c):
print('我是旧功能')
print(a, b, c)
return '执行旧功能成功'
old(1, 2, 3)
使用@函数装饰器名称来修饰原函数
一个函数可以被多个装饰器修饰,执行顺序为从近到远。