再看装饰器

其他资料
https://www.cnblogs.com/wkhzwmr/p/15282810.html
https://www.cnblogs.com/wkhzwmr/p/15085126.html

基本代码实现

用函数的形式来表示

def add(x,y):
    return x+y

def test(): # 调用add函数传入1,2参数
    print(add(1,2))

test() # 3

装饰器的形式

@log
def add(x,y):
    return x + y

实现过程类似以下的函数

没有更换函数的名字,用函数名来调用函数,就可以达到类似的扩展功能

def add(x,y):
    return x + y
add = log(add)

tips:AOP编程,面向切面编程。不修改目标源码的前提下,添加功能的计数手段或设计模式,是对OOP的补充;OOP面向对象编程

具体实现

比单纯的从一个函数中调用另外一个函数要复杂的多;
基本代码而且还没有函数参数传递;
函数装饰器是装饰器作用在函数上,原装饰器可用函数编写也可用类编写

通过包装函数间接调用原函数

def log(fn):
    def wrap(*args,**kwargs): # 原函数是装饰器下方的fn
        print(f'log:{args},{kwargs}')
        return fn(*args,**kwargs)
    
    return wrap # 返回包装函数替代原函数与名字关联

@log
def add(x,y):
    return x + y
add(1,2) # log:(1, 2),{}
# 打印出有打印的语句或return具体的返回值
print(add(1,2)) # log:(1, 2),{}   3

用匿名函数来写

def log(fn):
    #### fn.log_func为lambda生成的函数名,需调用
    fn.log_func = lambda *args, **kwargs:print(f'log:{args},{kwargs}')
    fn.log_func()
    return fn
@log
def add(x,y):
    return x + y
print(add(1,2)) # log:(),{} 3

类装饰器

任何可调用对象(callable)都可用来实现装饰器模式

class log:
    def __init__(self,fn):
        self.fn = fn
    
    def __call__(self,*args, **kwargs):
        print(f'log:{args},{kwargs}')
        return self.fn(*args, **kwargs)

@log
def add(x,y):
    return x + y
add(1,2) # log:(1, 2),{}
print(add(1,2))  #log:(1, 2),{} 3

把类装饰器添加到类中;不推荐

类实现的装饰器应用实例方法时,会导致方法绑定丢失

class X:
    @log
    def test(self): # #  方法被装饰器实例所替代 
        pass
x = X()

x.test # log:(),{}   
print(x.test) # <__main__.log object at 0x0000012D1FA50C50>  log:(),{}
try:
    x.test()
except Exception as e:
    print(e) # test() missing 1 required positional argument: 'self'

把函数装置器添加到类中

有无参数装饰器的本质区别在于,被修饰的原函数参数在外壳函数里(这里的log)还是内层函数中(这里的wrap)

def log(fn): # 
    def wrap(*args,**kwargs): # 通过包装函数间接调用原函数;原函数是装饰器下方的函数
        print(f'log:{args},{kwargs}')
        return fn(*args,**kwargs)
    
    return wrap # 返回包装函数替代原函数与名字关联
class X:
    @log
    def test(self):
        pass
x = X()
print(x.test) # <bound method log.<locals>.wrap of <__main__.X object at 0x0000020A715C0D30>
# 这里与类装饰器的结果不同的原因是
## 函数对象默认实现了描述符协议和绑定规则
print(log.__get__) # <method-wrapper '__get__' of function object at 0x0000019EC98497B8>

装饰器的嵌套

一个函数定义可以被一个或多个 decorator 表达式所包装。
当函数被定义时将在包含该函数定义的作用域中对装饰器表达式求值。
求值结果必须是一个可调用对象,它会以该函数对象作为唯一参数被发起调用。
其返回值将被绑定到函数名称而非函数对象

@f1(arg)
@f2
def func(): pass

等价于

func = f1(arg)(f2(func))

f1无参数的话就是

func  = f1(f2(func))

装饰器的参数

装饰器带有参数

def log(name='default'): # 外层函数接收参数
    print(f'args:{name}')

    def decorator(fn): # 装饰器
        print(f'decorator:{fn}')
        return fn # 返回包装函数或原函数
    return decorator

@log('demo')
def test():
    pass
# 装饰器已经运行了,执行结果如下
'''
args:demo
decorator:<function test at 0x00000183D84198C8>
'''

对于有参数的装饰器,即便是默认值也需加括号

@log()
def test():
    pass
## 执行结果如下
'''
args:default
decorator:<function test at 0x000002AFA47A9950>
'''

装饰器的属性

包装函数(装饰器)需更像原函数,如拥有某些相同的属性
装饰器functools.wraps将原函数__module__、namedoc、__annotations__等属性复制到包装函数
还用__wrapped__存储原始函数或上衣装饰器返回值。可据此判断并绕开装饰器对单元测试的干扰

import functools
def log(fn):
    @functools.wraps(fn)
    def wrap(*args,**kwargs): # 通过包装函数间接调用原函数;原函数是装饰器下方的函数
        # print(f'log:{args},{kwargs}')
        return fn(*args,**kwargs)
    print(f'wrap:{id(wrap)},finc:{id(fn)}')
    return wrap # 返回包装函数替代原函数与名字关联
@log
def add(x,y):
    return x + y

add.__name__ # wrap:1264671299784,finc:1264671299648
print(add.__annotations__) # {}
print(id(add),id(add.__wrapped__)) # 1264671299784 1264671299648

类型装饰器

类型装饰器是修饰类的
类型装饰器是直接作用在类型上的,与函数装饰器的区别是接收参数为类型对象(类)

装饰器-函数中返回类

def log(cc): 
    class wrapper:
        def __init__(self,*args, **kwargs):
            self.__dict__['inst'] = cc(*args,**kwargs)
        
        # 下方就是描述符吧,一点也看不懂
        def __getattr__(self,name):
            value = getattr(self.inst,name) # .inst啥意思
            print(f'get:{name}={value}')
            return value
        def __setattr__(self,name,value):
            print(f'set: {name} = {value}')
            return setattr(self.inst,name,value)
    return wrapper

@log
class X:
    pass
x = X()
x.a = 1 # set: a = 1
x.a # get:a=1

装饰器——函数中返回函数

间接调用目标构造方法创建实例

def log(cls):
    def wrap(*args, **kwargs):
        o = cls(*args,**kwargs) # 间接调用目标构造方法创建实例
        print(f'log : {o}')
        return o
    return wrap

@log
class X:
    pass

X() # log : <__main__.X object at 0x000001CB72B30A90>

装饰器的应用

调用跟踪

记录目标调用参数、返回值。以及执行次数和执行时间等信息

def call_count(fn):
    def counter(*args, **kwargs):
        counter.__count__ +=1
        return fn(*args, **kwargs)
    
    counter.__count__ = 0
    return counter
@call_count
def a():
    pass

@call_count
def b():
    pass
# 还可以用分号
# 记录了a调用了2次
a();a();print(a.__count__) # 2
# 记录了b调用了3次
b();b();b();print(b.__count__) # 3

标准库类似的应用,通过缓存结果减少目标执行次数

import functools
@functools.lru_cache(10)

属性管理

为目标添加额外属性,在原有设计上以装配方式混入(mixin)其他功能组

def pet(cls):
    cls.dosomething = lambda self:None
    return cls
@pet  # 添加宠物功能
class Parrot:
    pass

实例管理

替代目标构造方法,拦截实例的创建。用于实现对象缓存,或单例模式

def singleton(cls):
    inst = None
    def wrap(*args, **kwargs):
        nonlocal inst
        if not inst: inst = cls(*args, **kwargs)
        return inst
    return wrap

@singleton
class X:
    pass # 
print(X() is X()) # True

部件注册

在很多Web框架里,用装饰器替代配置文件实现路由注册

class App:
    def __init__(self):
        self.routers = {}
    def route(self,url): # 实现为带参数的装饰器,用于注册路由配置
        def register(fn):
            self.routers[url] = fn
            return fn
        return register

app = App()
@app.route('/')
def index():
    pass
@app.route('/help')
def help():
    pass

# 打印类中的routers属性
print(app.routers) # {'/': <function index at 0x000001B7F9129E18>, '/help': <function help at 0x000001B7F9129EA0>}

笔记来源《Python3学习笔记(上卷)》

posted @ 2021-11-20 12:53  索匣  阅读(21)  评论(0编辑  收藏  举报