python全栈-函数式编程
文章目录
函数式编程
主要包括:
- 高阶函数
- 匿名函数
- 闭包
- 偏函数
- map
- reduce
- sorted
- filter
高阶函数
一个函数的参数,可以是另一个函数,这个函数就是高阶函数
上面这四个函数都是内置的高阶函数
def fun():
print('fun')
def fun2(func):
print('fun2')
func()
fun2(fun)
这是一个无参的高阶函数fun2
def fun(a,b):
print('fun')
print(f'a+b={a+b}')
def fun2(func,*args,**kwargs):
print('fun2')
func(*args,**kwargs)
fun2(fun,5,6)
这是可以传参的高阶函数,只需要在原来的基础上,加上*args,**kwargs这个参数。我们就可以把参数从fun2传到fun里面去。
偏函数Partial function
使用functools模块
就是把函数的一些参数固定,设置默认值,然后返回新函数。再去使用新函数会更简单
- 进制转换
def int2(x,base=2):
return int(x,base)
print(int2('1'))
print(int2('100010'))
print(int2('100'))
int方法自带的有进制转换属性base,比如print(int(‘10000’,base=2))
可以把二进制的10000转换成十进制16.如果我们要大量去使用这种方法,就比较麻烦,所以自定义一个新函数int2去修改原来int的参数base,让他一直保存base=2.这个int2就是偏函数
上面就是实现了偏函数
然后python自带的库functools简化了自定义函数的步骤。用起来更方便了
import functools
int2 = functools.partial(int,base=2) 简化的偏函数,返回的是新函数,可以直接调用
print(int2('1'))
print(int2('100010'))
print(int2('100',base=10)) 随时可以再次修改属性
闭包closure
就是一个函数,函数体内是自由变量和内置函数
def 闭包函数:
自由变量
def 内置函数
- 自由变量:在闭包函数内部被定义,但是在内置函数外。就是之前学的对象的“内部函数的外部变量”
- 自由变量会一直存在,和全局变量一样的生命周期
- 同样的用法,在内部函数去修改自由变量需要nonlocal声明。仅访问自由变量就不需要声明
def outer(): 闭包
print("outer")
a = 10 自由变量,对于内部函数而言,自由变量属于父级函数
def inner(): 内部函数
print("inner")
print(a) 内部函数访问自由变量,使用了a,a才是自由变量
return inner 因为闭包外面无法调用内部函数,需要返回出去
inn = outer()
inn()
- 闭包的原理
- 在调用内部函数的时候需要先执行一次闭包函数这个外壳
- 此时,内部函数和自由变量放在一个内存空间。
- 当我们使用内部函数的时候,内部函数用到了自由变量
- 所以内部函数和自由变量会一直保存,直到这个闭包不在使用,也就是程序结束
- 在程序结束之前,这个自由变量会和内部函数待在一起。可以被修改
- 闭包的作用
- 隐藏变量,避免全局污染
- 全局变量能干的,自由变量也能干。
- 闭包的用处
- 解决全局变量滥用问题
- 解决了函数内局部变量无法自增的问题,因为自由变量的值,不会被销毁
- 当自由变量和全局变量相同的时候,不会互相干扰
自由变量不会被垃圾回收机制及时回收,可能会造成内存占用
- 定义闭包在原函数不变的情况下增加内容
def add(fun1):
def sum1():
print('新增功能') 新增功能
fun1() 调用原来的函数,实现原来的功能
return sum1
def sum():
print('sum')
sum = add(sum) 原函数传给闭包,把闭包里面的内部函数再赋值给变量
sum() 和原函数一模一样的调用方式,但是有了新功能
这里的原函数是sum
我们要新增一个print输出,类比项目成品里面新增功能
我们可以在原来的函数里面直接增加,那样就需要结合原函数,重新修改,比较麻烦
所以,我们把原来的函数传给闭包,让闭包里面的内部函数去调用原来的函数
同时在内部函数里面还能新增功能。
map(func,[,])依次运算
接收两种参数,一个是函数,一个是序列,就是我们给的数据
map函数将序列的值依次传递给函数,并把结果放在新的序列里面返回出来
功能也很简单,比如这个对序列所有数平方的操作
def add(x):
return x*x
ls = [1,2,3,4,5]
ls1 = []
for x in ls:
ls1.append(add(x))
print(ls1)
不难发现,我们需要准备两个列表,还要一个函数,以及一个循环,才能实现
def add(x):
return x*x
ls1 = map(add,[1,2,3,4,5]) 返回的数据对象是一个map对象,需要使用list序列化,就跟迭代器一样
print(list(ls1))
还可以使用匿名函数,简化函数创建的过程
ls1 = map(lambda x:x*x,[1,2,3,4,5])
print(list(ls1))
也支持多个序列作为参数
ls1 = map(lambda x,y:x*y,[1,2,3,4,5],[5,3,1])
print(list(ls1))
按照最短的那个序列为准。就像两个序列合并为字典一样,按照最短的那个序列为准
reduce(func,[,])累加
需要使用functools模块
和map的调用方式一样,一个函数参数,一个序列参数
reduce是从序列先取出两个参数,传给函数去运算,然后把运算的结果,和第三个参数传给函数运算,再拿结果跟后面的参数去运算
from functools import reduce
def add(x, y): 必须是两个参数才可以
return x + y 这是一个求和
numbers = [1, 2, 3, 4, 5]
result = reduce(add, numbers)
print(result) # 输出 15,1 + 2 + 3 + 4 + 5 = 15
本质是 f(f(f(1,2)3)4) == reduce(f,[1,2,3,4])
filter过滤
过滤函数:根据函数的判断结果,对序列内部的元素进行筛选
是内置函数,不需要导入
ls = [1,2,3,4,5,6]
def jishu(x): 判断奇数
return x%2==1
ls1 = filter(jishu,ls)
print(list(ls1)) 可以打印出被筛选过的序列
sorted([],key=func,reverse=Ture)排序
就是一个排序函数,正常来说,就是对序列排序,然后返还给你
也可以指定排序的依据,默认是从小到大,从左到右排
key可以指定比较的方式
reverse为真的时候,就是逆序
比较字符串的时候,是按首字母的ascll值排序
- 自定义比较方式
from functools import cmp_to_key 只有自定义的时候才需要导入这个库
ls = [1,2,3,4,5,6]
def daxiao(x,y):
if x > y:
return -1
if x < y:
return 1
return 0
ls1 = sorted(ls,key=cmp_to_key(daxiao))
print(ls1)
装饰器decorator
本质就是一个闭包函数,简化了一些操作
def outer(func):
def inner():
func()
print('新增功能')
return inner
def fun():
print('fun')
fun = outer(fun)
fun()
这是使用闭包对原有函数,新增功能
def outer(func):
def inner():
func()
print('新增功能')
return inner
@outer # fun = outer(fun)
def fun():
print('fun')
fun()
这是使用了装饰器,简化了操作
多个装饰器的执行顺序
def outer(func):
print('outer start')
def inner():
print('inner start')
func()
print('inner end')
print('outer end')
return inner
def outer1(func):
print('outer1 start')
def inner1():
print('inner1 start')
func()
print('inner1 end')
print('outer1 end')
return inner1
@outer # fun = outer(fun)
@outer1 # fun = outer1(outer(fun))
def fun():
print('fun')
fun()
执行结果是
outer1 start
outer1 end
outer start
outer end
inner start
inner1 start
fun
inner1 end
inner end
从结果上看,是outer1(outer(inner(inner1(fun))))
-
fun = outer1(outer(fun))
- 从这个关系来讲,outer1的参数是outer(fun),outer的参数是原fun
- 我们再次使用fun的时候,实际上是(outer1(outer(fun)))() 括号里面的是一个整体
- 我们想要执行函数,但是括号里面的整体还不是可执行函数。
- 然后我们来到了outer1这个函数,先是执行了outer1,拿到了返回值inner1,inner1=outer(fun)
- 此时了outer(fun)还不是可执行函数,需要先执行一次outer,拿到返回值才能执行
- 于是执行了outer,然后拿到了返回值inner,inner指向了fun
-
这个推导比较麻烦,直接记结论
-
先执行靠近原函数的装饰器里面的自由变量语句,再依次执行远离原函数的装饰器语句(不包括内部函数)
-
然后所有的装饰器都执行完,再从最远的内部函数开始执行,依次执行到最近的内部函数,然后执行原函数
-
最后内部函数由近到远,收尾
含参装饰器
def outer(br): 专门处理装饰器的参数
def decorater(func):
def inner(*args, **kwargs):
if br=='是':
print('Y')
else:
print('N')
return func(*args, **kwargs)
return inner
return decorater
@outer('是')
def fun():
print('fun')
fun()
需要在外部再包一层函数。最外层函数的参数,是@后面的参数。里面就是之前定义的装饰器了
类装饰器
和函数式的装饰器效果一样,功能一样。
class dec():
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print('新增功能')
return self.func(*args,**kwargs)
@dec
def add():
print("add")
add()
基本上就是这个模板了
缓存装饰器和计时装饰器
- 缓存装饰器
import time
class dec():
__dkt = {} # 定义一个类属性字典,键是函数的名称,值是函数的返回值
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print('新增功能')
if self.func.__name__ not in dec.__dkt:
dec.__dkt[self.func.__name__] = self.func(*args,**kwargs) # 把函数的返回值储存在字典里面
return dec.__dkt[self.func.__name__]
@dec
def add():
print("add")
time.sleep(1)
return 100
t1 = time.time()
print(add())
t2 = time.time()
print(add())
t3 = time.time()
print(add())
t4 = time.time()
print(f't2-t1={t2-t1}\nt3-t2={t3-t2}\nt4-t3={t4-t3}')
- 计时装饰器
import time
class dec():
__dkt = {} # 定义一个类属性字典,键是函数的名称,值是函数的返回值
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print('新增功能')
if self.func.__name__ not in dec.__dkt:
dec.__dkt[self.func.__name__] = self.func(*args,**kwargs)
return dec.__dkt[self.func.__name__]
def outer(func):
def inner(*args,**kwargs):
start = time.time()
func(*args,**kwargs)
end = time.time()
print(f'本次耗时{end-start}')
return func(*args,**kwargs) 需要返回原函数
return inner
@outer
@dec
def add():
print("add")
time.sleep(1)
return 100
t1 = time.time()
print(add())
t2 = time.time()
print(add())
t3 = time.time()
print(add())
t4 = time.time()
print(f't2-t1={t2-t1}\nt3-t2={t3-t2}\nt4-t3={t4-t3}')
这是缓存装饰器+计时装饰器。以后遇到这个需求,套模板即可。
生成器generator
延时计算,边计算边给数据
ls = [x for x in range(1,10)]
print(ls)
ls1 = (x for x in range(1,10))
for x in range(3):
print(ls1.__next__())
列表推导式,是把列表一次性处理完,再返回出来。无论这个列表用几个数据,都是先算完。
生成器,是用几个数据,就给几个数据,不用的不算。
这里我使用了for循环,次数是3.
后台的生成器,也只生成了3个数据给我。不浪费内存空间,不占用过多内存。
正是这个原因,每次循环都要重新调用生成器。会造成时间的浪费。所谓时间换空间。
使用next(生成器),和生成器加下划线name是一个原理
生成器函数
一个函数包含yield关键字,这个函数就是生成器对象了。
本质是利用yield关键字,一次只返回一个结果。
程序执行到yield就阻塞。当外部调用next方法时,才进行下一次运算
生成器函数遇到return语句就会终止,并抛出异常,异常的提示信息就是return语句后面的东西
def test():
i = 6
print('start')
while i<11:
yield i 第一次运行到这里结束,把i传出去
i+=1
print('end')
return '用完了'
a = test()
print(a) 可以查看生成器的类型
print(a.__next__()) 调用生成器一次,到yield语句,并把yield变量的值传递出去,输出6
print(a.__next__()) 从yield语句下面开始,再到yield停止 输出7
print(a.__next__()) 输出8
当生成器里面的循环结束的时候,如果还在调用生成器,就会报错,把return后面的语句打出去
- 生成器的短处
def test():
i = 6
print('start')
while i<11:
t = yield i 程序是在yield i这个语句停止的,严格来说是return (yield i)
i+=1
print(t)
print('end')
return '用完了'
a = test()
print(a)
print(a.__next__()) 唤醒生成器
print(a.__next__())
print(a.__next__())
因为生成器遇到yield就停止。然后i的值也传了出来。我们下一次执行生成器的时候,是从temp=开始的,后面的值是上一次生成器的内容。他们没办法直接赋值。所以temp就没有值了。
只是temp接收不到i的值,i自己的值还是有的。
生成器send方法
唤醒生成器,并发送信息到生成器内部。
def test():
i = 6
print('start')
while i<11:
t = yield i
i+=1
print(t)
print('end')
return '用完了'
a = test()
print(a)
print(next(a))
print(a.send('传给生成器'))
print(next(a))
print(next(a))
send本身也是一个唤醒器,就是可以传值进去。
使用next的时候,在生成器内部的t 没有自己的值,因为值被输出了。现在send又把一个变量传进去了,位置是之前yield所在的位置。所以t 可以获取到值了。
也只是这一次有了自己的值。下一次还会因为yield变量被传出来,而变成None。
生成器可以使用for遍历,遍历到生成器无法再次使用next
迭代器lterator
- 特点
- 只能往前,不能后退
- 有两个基本方法:next,iter
- 可以记住遍历位置的对象
- 从第一个开始访问,直到最后不能访问
可迭代对象与迭代器
实现了iter方法的对象是可迭代对象leratable
同时实现iter和next方法的对象是迭代器lterator
- 列表,字典,字符串是可迭代对象,不是迭代器
下面是判断,一个对象是不是可迭代对象,和判断一个对象是不是迭代器。返回值是布尔值
from collections.abc import Iterable, Iterator
a = isinstance([],Iterable)判断参数一是不是可迭代对象
b = isinstance([],Iterator)判断参数一是不是迭代器
print(a)
print(b)
使用iter方法,可以把可迭代对象转化成迭代器
from collections.abc import Iterable, Iterator
a = isinstance([],Iterable)
b = isinstance(iter([]),Iterator) 把可迭代对象转化成迭代器
print(a)
print(b)
- 迭代器也是惰性对象,用一个数据出一个数据。
- 生成器一定是迭代器。
- 序列一般是可迭代对象
- 可迭代对象可以变成迭代器
- 迭代器可以当可迭代对象去使用
- 迭代器可以使用next方法去获取值。
for循环的本质
对python3而言,for循环的本质也是一个迭代器,每次循环就是调用一次next方法
ls = iter([4,6,8,9,3])
while True:
try:
x = next(ls) 获取迭代器的值,一次取一个
print(x)这是我们拿到单个值进行的运算
except StopIteration: 用完数据就退出
break
创建迭代器
使用类来创建迭代器
需要重写iter方法和next方法
iter需要返回一个迭代器对象
next方法就是返回下一个值
StopIteration迭代器专属异常报错
class myiterator():
def __iter__(self):
self.number = 11
return self
def __next__(self):
if self.number < 100:
self.number += 11
return self.number
else:
raise StopIteration手动抛异常
ls = iter(myiterator())
# ls = myiterator().__iter__() 类似初始化,把可迭代对象变成迭代器
print(next(ls))
print(next(ls))
print(next(ls))
print(next(ls))
给类对象添加属性和方法
class person():
def __init__(self, name, age):
self.name = name
self.age = age
p1 = person("John", 20)
p2 = person("Jimi", 25)
def run(self):
print(f'{self.name} is {self.age} years old')
# 添加对象属性
p1.number = 10
print(p1.number) 成功
p1.run = run() 失败
p1.run() 失败
如果我们直接使用赋值的方式,只能给对象添加属性。不能添加方法
import types
class person():
def __init__(self, name, age):
self.name = name
self.age = age
p1 = person("John", 20)
p2 = person("Jimi", 25)
def run(self):
print(f'{self.name} is {self.age} years old')
# 添加对象属性
p1.number = 10
print(p1.number)
p1.run = types.MethodType(run, p1) 只能使用这个方法,动态添加方法到对象里面,参数一是方法,参数二是类对象
p1.run()
给类动态增加类方法
直接使用静态装饰器staticmethod和类装饰器classmethod去定义方法,直接就能添加到已有的类里面
class person():
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def add():
print('add')
person.add = add
@classmethod
def run(cls):
print(111)
person.run = run
person.add()
person.run()
slots方法
只对实例对象的动态添加属性和方法有限制。
对类对象,没有限制。
只对slots所在的类有限制,对子类没有限制
class person():
__slots__ = {'name','age'} # 类的实例化对象只能在本类中增加这两个属性
def __init__(self, name, age):
self.name = name
self.age = age
p1 = person("John", 20)
p1.num = 10 报错了,不让添加
就是只能添加slots里面的属性。不能添加别的。
python小技巧
- 格式化字符串f ‘{}’’
a = 10
print(f'。。。{a}。。。') 结果是10 只有变量的值被打印出来了
print(f'。。。{a=}。。。') 结果是a=10 是 变量=值 这种格式打印出来
print(f'{你好:-^20}') 打印结果就是----------你好----------,居中打印变量,空位用^前面的符号填满
print(f'{你好:->20}') 内容靠右打印
print(f'{你好:-<20}') 内容靠左打印
b = 12.345
print(f'{b:.2f}') 就可以直接对b进行保留小数点后两位的操作了,更方便了
- 删除前缀removeprefix/删除后缀removesuffix
aa = 'helloworld'
print(aa.removeprefix('hello')) 输出world
print(aa.removesuffix('ld')) 输出hellowor
如果参数里面的字符不是aa的前后缀,那么aa将正常原始内容输出,不会缺失
- mapping,我们在使用字典的时候,可能会单独使用keys或者values。使用keys.mapping,可以得到原字典的内容,可以被输出。
- 字典合并:两个字典使用或| ,就可以得到一个合并的字典了。字典1= 字典1|字典2.还可以简写成: 字典1|=字典2
- match和case。我们在使用switch…case语句的时候,在case语句里面需要使用break分隔每个case的代码。现在只需要把switch变成match。case里面就不需要break隔断了。其余保持不变

303

被折叠的 条评论
为什么被折叠?



