Python装饰器应用实例
一丶写一个命令分发器
1.要求:程序员可以方便的注册函数到某一命令,用户输入命令,路由到注册的函数,如果此命令没有对应的注册函数,执行默认函数
拿到这个题目,又是一脸懵逼
分析:题目要求大概可以分成两个部分,注册函数,执行函数。
# 注册函数
def command():
functionname = {}
def register(name): #注册函数
def wrapper(fn):
# functionname[fn.__name__] = fn 如果这样设置的话,路由的函数名只能是函数的__name__
functionname[name] = fn
return fn
return wrapper
def defaultfunc(): # 默认函数,用来处理仓库命令里没有cmd时的执行函数
print(" Unknown cmd!")
def run():
cmd = input(">>")
cmd = cmd.strip()
return functionname.get(cmd,defaultfunc)()
return register, run
register, run = command()
@register("add")
def add():
print("register!")
2.完善上面的命令分发器,实现函数可以带任意参数(可变参数除外),解析参数并要求用户输入
思路:
1°注册的时候固定死函数的参数
2°运行时,通过input输入的字符串获取参数
下面先来实现第一种:
def command():
functionname = {}
# 注册函数
def register(name, *args, **kwargs):
def wrapper(fn):
# 将函数和参数收集,用的时候解包即可
functionname[name] = fn, args, kwargs
return fn
return wrapper
# 默认函数
def defaultfunc():
print("Unknown cmd!")
# 执行函数
def run():
cmd = input(">>")
# 因为可能存在找不到cmd的情况,讲缺省值设为一个三元组用来解包, 用来执行default()函数
fn, args, kwargs = functionname.get(cmd,(defaultfunc, (), {}))
fn(*args, **kwargs)
return register, run
register, run = command()
@register("add1",100,200)
@register("add2",10,20)
@register("add3",1,2)
def add(x, y):
print("register!")
return x + y
下面来实现第二种,通过input的收集来获取参数:
def command():
functionname = {}
# 注册函数
def register(name):
def wrapper(fn):
# functionname[fn.__name__] = fn 如果这样设置的话,路由的函数名只能是自己的名字
functionname[name] = fn
return fn
return wrapper
# 默认函数
def defaultfunc():
print(" Unknown cmd!")
def run():
cmd = input(">>")
cmd, *params = cmd.replace(","," ").split() # 参数解构
args = [] # 用来收集位置参数
kwargs = {} # 用来收集关键字参数
for i in params: # params类似[1,"x"=2]
item = i.split("=") # 返回值为list
if len(item) == 1:
args.append(int(i)) # 假设用户想输入的是数字
else:
a = []
item[1] = int(item[1]) # 假设用户想输入的是数字
a.append(item)
kwargs.update(a)
return functionname.get(cmd,defaultfunc)(*args, **kwargs)
return register, run
register, run = command()
@register("add")
def add(x,y=100):
print("register!")
return x + y
很多Python Web框架使用这样的装饰器把函数是添加到某种*注册处,例如把URL模式映射到HTTP响应的函数上的注册处,这种注册装饰器可能会也可能不会修改被装饰的函数。
二丶实现一个cache装饰器,可先可过期被清除的功能
数据类型的选择:
缓存的应用场景,是有数据需要频繁查询,且每次都需要大量计算或者等待时间之后才能返回的结果的情况,使用cache缓存来提高查询速度,用内存空间换区查询、加载的时间。
cache应该选择什么数据结构?
- 便有查询的,且能快速获取数据的数据结构。
- 每次查询的时候,只要输入一致,就能得到同样的结果(这里的输入一致,对于functools中的lru_cache来说就非常严格,但有些时候,我们只要求形参对应的实参的值一致就满足要求,这将是我们下面make_key的重点)。
- 我们应该选择一个映射结构,这里索性就选择字典,key是参数列表组成的结构,value是函数返回值。
key的存储
- 字典的key必须是hashabke对象,key能接收位置参数和关键字参数传参
- 可以将形参和实参绑定在一起,放在字典params_dict中,最后取出items对,实参不可以出现unhashable对象
key的算法设计
- inspect模块获取函数签名后,取parameters,这是一个有序字典,会保存所有参数的信息。
- 构建一个字典params_dict用来存放函数的实参和形参绑定在一次,组成kv键值
- 如果使用了缺省值的参数,不会出现在实参params_dict中,会出现的parameters中,缺省值也在函数定义中,因此要先将paramster中的带有缺省值的kv存在在字典中
# 代码实现make_key
def make_key(fn,args, kwargs):
params_key = {}
sig = inspect.signature(fn) # 制作签名
params = sig.parameters # 得到属性值,是一个键值对
# 首先现将所有形参和对应的缺省值的kv对放到params_dict中
for j in params.keys():
params_key[j] = params[j].default
# 下面的这种更新params_key略显臃肿
# params_key.update(zip(params.keys(),args)) # 将顺序传参的参数和形参按位置对应关系绑定在一起
# params_key.update(kwargs) # 将传的关键字参数放入dict_key中
# sig.bind(*args, **kwargs) 将函数实参和形参绑定在一起
bound_values = sig.bind(*args, **kwargs).arguments # 返回值为OrderDict
params_key.update(bound_values.items())
return tuple(params_key.items())
有了make_key我们就可以很轻松的实现想要的cache功能
def make_key(fn,args, kwargs):
params_key = {}
sig = inspect.signature(fn) # 制作签名
params = sig.parameters # 得到属性值,是一个键值对
for j in params.keys(): # 先用用缺省参数更新一下dict_key
params_key[j] = params[j].default
# 下面的这种更新dict_key略显臃肿
# params_key.update(zip(params.keys(),args)) # 将顺序传参的参数和形参按位置对应关系绑定在一起
# params_key.update(kwargs) # 将传的关键字参数放入dict_key中
# sig.bind(*args, **kwargs) 将函数实参和形参绑定在一起,下面更新到字典中
bound_values = sig.bind(*args, **kwargs).arguments
params_key.update(bound_values.items())
return tuple(params_key.items())
def decorator(fun):
cache = {}
@functools.wraps(fun)
def wrapper(*args, **kwargs):
key = make_key(fun,args, kwargs)
# 查询cache是否存在已有的键值对
if key in cache:
return cache[key]
cache[key] = fun(*args, **kwargs)
return cache[key]
return wrapper
@decorator
def add(x=100,y=100):
time.sleep(2)
return x + y
过期功能
一般缓存系统都有过期功能。
过期是什么?
他是某一个key过期。可以对每一个key单独设置过期时间,也可以对这些key统一设定过期时。
本次的实现采用同一设定key过期的时间,当key的生存超过了这个时间,就自动被清除。
注意:这里并没有考虑线程等问题。而且这种过期机制,每一次都要遍历所有数据,大量数据的时候,遍历可能有效率问题。
清除的时机
何时清除过期key?
1.用到某个key之前,先判断是否过期,如果过期重新调用函数生成新的key对应value值。
2.一个线程负责清除过期的key,这个以后实现,本次在创建key之前,清除所有的key。
value的设计
1、key =>(v,createtimestamp)
适合key过期时间都是统一的设定。
2、key => (v,createtimestamp,duration)
duration是过期时间,这样每一个key就可以单独控制过期时间。在这种设计中,-1表示永不过期,0表示立即过期,正整数表示持续一段时间过期。
本次采用第一种实现。
def make_key(fn,args, kwargs):
params_key = {}
sig = inspect.signature(fn) # 制作签名
params = sig.parameters # 得到属性值,是一个键值对
for j in params.keys(): # 先用用缺省参数更新一下dict_key
params_key[j] = params[j].default
# 下面的这种更新dict_key略显臃肿
# params_key.update(zip(params.keys(),args)) # 将顺序传参的参数和形参按位置对应关系绑定在一起
# params_key.update(kwargs) # 将传的关键字参数放入dict_key中
# sig.bind(*args, **kwargs) 将函数实参和形参绑定在一起,下面更新到字典中
bound_values = sig.bind(*args, **kwargs).arguments
params_key.update(bound_values.items())
return tuple(params_key.items())
def cache_experid(duration = 5):
def decorator(fun):
cache = {}
@functools.wrap(fun)
def wrapper(*args, **kwargs):
# 每次使用前,批量清除过期的cache,cache是一个字典,切记,不能边遍历边修改
expired = []
for i in cache:
nowstamp = datetime.datetime.now().timestamp()
if nowstamp - cache[i][1] > duration:
expired.append(i)
for j in expired:
cache.pop(j)
key = make_key(fun,args, kwargs)
if key in cache:
return cache[key]
cache[key] = fun(*args, **kwargs), datetime.datetime.now().timestamp()
return cache[key]
return wrapper
return decorator
@cache_experid()
def add(x=100,y=100):
time.sleep(2)
return x + y
装饰器的用途
装饰器是AOP面向切面变成Aspect Oriented Programming的思想的体现。
面向对象往往需要通过集成或者组合依赖等方式调用一些功能,这些功能的代码旺旺可能在多个类中出现。
装饰器应用在日志、监控、权限、审计、参数检查、路由等处理。
这些功能和业务功能无关,是很多业务都需要的公共的功能,所以独立出来,需要的时候,对目标进行增强。
上一篇: 二进制转化为十进制
下一篇: 【剑指offer】_13 圆圈中最后的数