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的思想的体现。
面向对象往往需要通过集成或者组合依赖等方式调用一些功能,这些功能的代码旺旺可能在多个类中出现。
装饰器应用在日志、监控、权限、审计、参数检查、路由等处理。
这些功能和业务功能无关,是很多业务都需要的公共的功能,所以独立出来,需要的时候,对目标进行增强。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值