2019/10/15 03-装饰器习题cache和命令分发器实现

在这里插入图片描述
装饰器要多练,后面用的框架都是一装饰就能用了,无非就是有参无参两种,可以通过柯里化转换出来,多参转成无参可以用偏函数
(前年仿照functools里面的lrucache,最近最少使用,模仿着写了一下,这个cache要求实现可过期,被清除的功能,这时候需要把时间引入
可以不考虑缓存满了之后的问题
先把核心代码实现

这个就是一个makekey的问题,无非就是凑tuple,就是个顺序问题
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
缓存的应用场景,缓存需要有价值
数据需要频繁查询,且每次查询都需要大量计算或者等待时间之后才能返回结果的情况,使用缓存来提高查询速度,用空间换时间
在一段时间内数据查询不变,但是计算效率比较高,这样是比较适合缓存的
使用redis要知道这种缓存是放在什么场景下使用的
缓存的目的就是空间换时间

在这里插入图片描述
计算机体系里,从cpu开始,cpu有寄存器,往下,一级缓存,二级缓存,三级缓存,再往下到内存,到内存就很慢了,内存已经不在cpu内了
cpu有寄存器,1,2,3级缓存,三级缓存共享,一个比一个慢,到了内存更慢,但是cpu拿数据都在内存当中,只要是应用程序,包括操作系统都必须在内存中运行,因为操作系统是个特殊的程序,硬盘是做持久化用的,不是跟cpu交换的,数据得先从硬盘到内存中
网络慢,硬盘慢,内存也慢
只要问到缓存,就是空间换时间

cacahe应该选用什么数据结构
只要每次查询输入是一致的,就可以用到缓存
数据结构应该是个字典

在这里插入图片描述
key是参数列表组成的结构,value是函数返回值,难点在于key如何处理。
缺省值可以通过inspect,签名,参数里面拿到,签名的数据结构是个有序字典,可以帮你解决顺序问题
函数调用时是有缺省值帮助的,但是函数参数实际是一样不能少(实际是有缺省值帮你传上参数了,一个都不能少)

在这里插入图片描述
下面的本质都是4,5在这里插入图片描述
key应该是个可哈希的,value是函数返回值,难点在于key如何处理
在这里插入图片描述
在这里插入图片描述
**key必须是可哈希的。
key能接受到位置参数和关键字参数传参。(函数传实参可以解构,参数可以解构)
位置参数是被收集在一个tuple中的,本身就有顺序。
关键字参数被收集在一个字典中,本身无序,这会带来一个问题,传参的顺序未必是字典中保存的顺序。如何解决?
orderDict
orderDict可以吗?可以,它可以记录顺序。
不用orderedDict?可以,用一个tuple保存排过序的字典的item的kv对
**

在这里插入图片描述在这里插入图片描述
上面4种,可以有几种理解
第一种:1,2,3,4都不同
第二种:3和4相同,2和3都不同
第三种:1,2,3,4全部相同

如果我们认为这些全部相同
在这里插入图片描述
key必须是可hash的
key是由传入的实参组合来得到的,作为字典的key一定要求可hash,key如果放个列表进来就肯定hash不了

在这里插入图片描述
送了这个就直接抛出异常,不允许出现不可hash的在这里插入图片描述
如何把key弄成一样
用inspect模块去看这个函数,把签名下面函数的参数列表,所有的参数拿到,参数本身是个有序字典,而且所有参数都在,我们就可以构建出一个参数字典,先按照位置顺序,把你收集到的位置参数依次对应起来,位置参数弄好,就要把你传入的实参拿进来处理好之后,还需要处理缺什么,就靠缺省值补充,这样就可以得到所谓的参数列表,这个参数列表就把所有的实参和形参的名字凑在一起,得到了一个个kv对,但是有可能这个kv对,有可能是无序的,排个序即可

在这里插入图片描述
所以分三个部分,1处理有序的,2.处理kwargs,3.使用缺省值把缺省的东西补齐了,这就是makekey要做的事情

调用的方式,最好使用lru的方式,让调用者查询不到在使用缓存在这里插入图片描述在这里插入图片描述
准备在这个基础上去做处理,可以把函数传进来的实参调用,可以对它进行参数的检查
装饰器一搭建,add返回给wrapper,

在这里插入图片描述
返回给wrapper,add现在就是wrapper,传实参,实参就被wrapper的函数收集起来在这里插入图片描述
所以接下来就是参数处理,构建key,先把所有参数收集完(把实参和形参全部配对,一个形参都不能留下,可变参数,keyword-only先不考虑),
首先要处理位置传参,这些东西都在args里面,args前面,就又变成values=签名
签名=inspect.signature去fn里拿一下

在这里插入图片描述
参数params,拿到一个有序字典,形参‘
然后从params,调用values,values拿到的也应该是有序的

在这里插入图片描述
现在要循环,就要开始调用了,因为送几个你不知道,所以就用顺序方式来做,你也可以遍历参数列表(用它的key往后找,它是有序的,一个一个迭代,当把args耗尽)
在这里插入图片描述
这是一种方式,遍历出来的值跟这里的key一个个配对就跟zip拉链一样,从args里一个个拿出来,从参数params拿出来一个个配对,zip有个特点,当args走完,后面就不配对了
在这里插入图片描述
可以这么做,用zip,配对之后刚好是二元组在这里插入图片描述
这样写起来更加python,用这种方式就解决了位置传参的问题在这里插入图片描述在这里插入图片描述
args在上面已经解决了,剩下的除了位置传参,下面还会用关键字传参,被收集到kwargs在这里插入图片描述
现在args和kwargs都处理好了,剩下就是缺省值,上面两种可能是空的
走到这一步代表,实参传进来都已经更新到里面了,已经有 的就不能用了

在这里插入图片描述
一个参数最全的,params,定义的所有参数都在这,k就是形参的名字,如果这个形参的名字在params_dict,就什么都不做了,如果not in,说 明没有传参
要把params_dict[k]=等于缺省值,v有4个重要属性,name,kind,default,annotation,把default拿出来就好

在这里插入图片描述
不管有没有用缺省值,我们所有参数都凑字典里了

现在就可以开始构造key了,这是一个普通字典,无序的,params_dict
可以用sorted排序(返回的是列表,所以要加tuple),二元组是第一个元素,排序完,套一个元组tuple,这个tuple,是二元组,二元组挨在一起的
如果中间传的是列表,中间在x=tuple转换的时候就会报错,不可哈希,凑完tuple,想做为key,hash的,self.hashvalue=hash(传进来的tuple)

在这里插入图片描述
就是这句在这里插入图片描述
key如果这样没什么问题,关键是要把它塞到字典里,作为key就出问题了,不可hash
在这里插入图片描述
现在就把顺序解决了
在这里插入图片描述
如果这样写add进来后,第一次进来之后,这个是空的,在这里插入图片描述
只能补缺省值,x=4,y=5在这里插入图片描述
如果写4,5
在这里插入图片描述
都在args里,就和前面的key配对了,缺省值就轮不上了
XY not in ,就进不去了
在这里插入图片描述
试着打印一下key,执行
在这里插入图片描述
加items

在这里插入图片描述
在调用一次,用add(4,5)
在这里插入图片描述
y=5,x=4在这里插入图片描述
三种都一样,key一样,value就是一样
在这里插入图片描述
key有了,下面就要看问题所在,
这一句应该放外面合适

在这里插入图片描述
如何调用lru_cache,每一次调用先算key,得到key首先要查字典,在不在local_cache里,如果不在 not in说明从来没缓存过,就要把key放进来,
在这里插入图片描述
需要return local_cache[key],意思是如果不在里面,执行下面的值塞到里面去,return是,不管在不在,里面保证有一个值,这样就解决了缓存问题
在这里插入图片描述
现在加个时间,一般import推荐换行写,但是也可以换成一行写
在这里插入图片描述
每个调用加一下时间

在这里插入图片描述
后面两个瞬间出来说明缓存有效

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
中间加点逻辑注释
在这里插入图片描述
这个才解决了key相同的问题,下面要解决过期的问题,就跟时间有关
使用datetime,类型丰富点

在这里插入图片描述
怎么知道什么时候过期,可以用元组结构,复合一下,元组是更加轻巧的结构,用字典也可以(用字典其实是用key),
跟现在这个key去查其实没什么区别,再搞字典就繁琐了

在这里插入图片描述
一种时间记录的是当下时间,一种是记录消亡时间,记录结束时间也有好处,
如果想要这条缓存记录,今晚12点到期,这种到点清除就适合用记录结束时间,如果规定放进去的数据,每5分钟清理一次,放起始时间就是差值超过5分钟,

在这里插入图片描述
现在时间记录了,但是什么时候清除,什么时机是 个难点
要么是用的时候看它一眼,其他没用的过期怎么办,还需要去扫描一下(学了多线程,并发的时候,就可以一遍做这个事情,还可以专门起一个线程,盯着现在这个东西,做清理)
这么做,只要你有调用,先遍历一遍,把里面的东西过期的清除干净,
把v解构了,一个是值,另一个是时间,从v里面解构出来,现在需要判断时间,两个时间相减得到一个timedelta,timedelta有个重要的方法totalseconds,但是这样比较麻烦
可以在下面timestamp,时间戳直接告诉你多少秒,微妙,就是个小数,小数可以保存

在这里插入图片描述
拿时间都是纳秒级的,如果是毫秒,微妙的你就需要考虑了,值不值得,
减去现在的值减去记录的时间戳,大于5秒,就清理掉
但是这么写,在迭代的时候keys,values,items,字典是不允许改变size的,这样删除,size就起变化 了,一定报错

在这里插入图片描述
在外面把符合条件keys,append到一个列表去
tuple这个数据结构少内存,能用tuple就尽量不要使用其他的数据结构,而且tuple也禁止别人随意改动,标准库也是能使用tuple就使用tuple

在这里插入图片描述
遍历过期的keys,字典有个方法可以用pop,key的方法,pop是不要这个返回值,但是要记得规模大,用遍历,即时多线程还是很费事
在这里插入图片描述
现在看起来有点难看,再继续修改,如果是内部不想给人使用可以用_下划线开头
可以抽象出来东西
在这里插入图片描述
定义这个函数

在这里插入图片描述
需要缩进,不然这个封装的函数在外面
在这里插入图片描述
挪动剪切
在这里插入图片描述
复制到这
在这里插入图片描述
位置放的不是很好,提取还可以在这里插入图片描述
现在是传两个参数进去,得到一个key
在这里插入图片描述
拿到这个key,继续在下面找
在这里插入图片描述
其实函数都可以写外面,是为了复用的,只不过我们写里面为了好看,clear_expire 过期清理,都不用关心上面函数怎么写的,通过名称就可以知道干嘛的,
如果函数调用,不管有没有缓存,先把过期的干掉

在这里插入图片描述
然后make key,然后看看是否在缓存中,不在缓存中就加进去
在这里插入图片描述
现在就只能做成这样,在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
这块我们是用zip
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
加个装饰器,如果要看到底执行了多久
在这里插入图片描述
等于多装饰器的应用了,多装饰器 执行顺序是有底向上,mag_cache先执行,后执行logger
在这里插入图片描述在这里插入图片描述
add变成了这边的wrapper、这个wrappe是个函数在这里插入图片描述
wrapper可以作为logger传入的参数
在这里插入图片描述在这里插入图片描述在这里插入图片描述
作为这个fn,就把里面的wrapper也替换掉了,这个是名称文档都替换掉了
在这里插入图片描述
缺打印功能,补一个,缺cache功能,补一个,缺什么补什么
在这里插入图片描述
现在只需要理解add的业务是做什么用的,剩下的功能看名字就知道是做什么的了
在这里插入图片描述
跑一下试试,3s,0s,0s
在这里插入图片描述
写的装饰器都是给函数附加一下改变的,装饰器的执行顺序就是有底向上的
在装饰器打印print来区分谁先执行就可以了
第一个装饰器返回给wrapper交给了第二个装饰器,第二个装饰在wrapper的基础上再包了一层wrapper,就跟洋葱一样,包,最后都到原有的被包装函数上去执行了,所以多个装饰器没有什么问题

在这里插入图片描述在这里插入图片描述
可以定制化之间先要过期的时间
在这里插入图片描述在这里插入图片描述
定义过期时间,但是要传参数进来,就需要柯里化
在这里插入图片描述
柯里化,整体右移在这里插入图片描述
外层加个函数
在这里插入图片描述
在这里插入图片描述
这边调用要加参数,否则用默认值5秒在这里插入图片描述
也可以每一条设置过期时长
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
如果使用有序字典,要注意,顺序要以签名声明的顺序为准,有序字典是和输入顺序有关,把有序字典所有的二元组拿出来,用sorted排序,就解决问题了
有序字典不是排过序,有序字典是跟输入顺序相同,并不代表排过序,我们排序就解决了所有问题。
使用有序字典的时候,就需要注意使用签名的顺序,要么就用组成好的顺序之外,用它的话,可以把里面items方法调用一下,得到一个可迭代对象,然后sorted就行 了

在这里插入图片描述在这里插入图片描述

写一个命令分发器

写函数注册到分发器,下一回运行起来后,就可以输入命令,如果命令整好对应到注册时用的名称,名称找这个函数,一致行,就相应的处理
**在这里插入图片描述**
有这么几件事,
第一,完成注册,让名称和函数对应起来,所谓的注册就是给字典里添加东西,所谓的对应就是映射
第二,如果此命令没有对应的函数,就执行默认函数,key不存在,抛出异常,打印一个缺省函数

reg命令注册,注册需要有个字典,commands={}(想要跑起来,找到key对应的函数),如果没找到就调用缺省的,不然就抛出异常
在这里插入图片描述
前面如果分发的话,里面应该做大循环,(敲命令敲对了,就执行函数),’'空号代表想退出,break即可,负责,代表没出去,没出去说明真的敲了命令,就需要去commands里面去找,用get方法,key就是cmd,找不到就用defaultfn做这个事情,前面是函数对象,要加括号调用起来(),调用起来才能打印print(unknown command)在这里插入图片描述

这就是讲的字典的应用,常见的get方法,以及key,如果key没找到就默认返回none(补了一个缺省函数defaultfn),前面返回应该都是可调用对象
在这里插入图片描述
套一个函数好看点,结构已经出来了,不会写装饰器没关系
在这里插入图片描述
写点函数,foo1,foo2,,reg注册
在这里插入图片描述
如果起的名字一样该怎么解决,是两个不同的用户,这时候该怎么办,(可以让每个用户有自己的command)如果不允许有自己的command,就需要覆盖,谁后注册就是谁的
还可以后注册的人,如果冲突可以,告诉注册不成功,是唯一性的
解决问题,提示此key已经存在或者直接覆盖,在业务中就需要做key的检查了,看key是否已经存在,告诉它,最好是提示它,直接覆盖的方式,如果直接覆盖的话,用户自己会不知道,就需要友好提醒,已经用过了,不允许重复,只能修改或新增
(不同用户绑定起来什么命令,不同用户也会名字冲突)

在这里插入图片描述
用户注册进去,底下循环起来在这里插入图片描述
dispatcher会走一个大循环
在这里插入图片描述
运行起来现在就成了
在这里插入图片描述
使用装饰器

在这里插入图片描述上面的需要做柯里化(整体往右移),要分析具体装饰器返回谁,里面return返回谁要特别注意
在这里插入图片描述在这里插入图片描述
然后到下面创建函数对象,然后函数对象对应的标识符作为参数歘进来
在这里插入图片描述
这块调用完,再处理下面的
在这里插入图片描述
这块执行后,才会执行下面函数的创建
在这里插入图片描述
这一步执行函数
在这里插入图片描述
顺序是由上至下处理的
这个整体是完成一个功能的

在这里插入图片描述
把它整体变成一个东西
在这里插入图片描述
上面是个整体在这里插入图片描述
里面定义好抛出,外面直接解构使用,拿到里面的对象在这里插入图片描述
也可以写成这样在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
重复注册,要不要抛出异常,看需求
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
**装饰器是面向切面的的编程,AOP的思想提现。在切面的时候做功能增强,并不会在函数内部做任何改动,在需要AOP的地方,类或方法上切下,前后的切入点可以加入增强的功能,让调用者和被调用者之间是解耦(不要挤在一起)
这是一种不修改原来业务代码,就能给程序动态添加功能的技术。里面 源代码是硬编码,里面没写的就是非切入式的增强 **在这里插入图片描述
比如在函数之前判断你是不是要求的用户,有没有权限,参数检查共同的功能适合抽象成装饰器函数,来需要的时候加载
在这里插入图片描述
无参和带参之间就做了一次柯里化,柯里化就是右移,加return,关键是你会不会用装饰器搭架子,内存的return一定想清楚
需要写装饰器等价式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值