欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python装饰器应用实例

程序员文章站 2022-07-15 08:26:30
...

一丶写一个命令分发器

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