python 装饰器1 理解到深入【类装饰器】【装饰器是类】

  1. 汇总
# 0. 装饰器的参数
@deprecated(1,2)  # 参数是1,2
def a_test():
    pass

@deprecated  # 参数是下面的函数
def a_test():
    pass

def deprecated(substitute, hint=SSH_PROCESS_HINT):
    def deprecation_decorator(func):
        func_name = func.__name__
        deprecation_message = DEPRECATION_MESSAGE.format(feature=func_name,
                                                         substitute=substitute,
                                                         hint=hint)

        @wraps(func)
        def wrapper(*args, **kwargs):
            trace = '{}:{}'.format(*_find_trace())
            logger.warning(deprecation_message.format(trace=trace))
            return func(*args, **kwargs)

        return wrapper
    return deprecation_decorator



# 1. 装饰器@wrapper 用在定义的地方【函数,类】
@wrapper
def func():
  pass

@wrapper
class Person:
    sex = 'man'


# 2. 不能直接变量上面
@wrapper
test



# 3. 嵌套定义函数的坑
def check_used_time(timeout):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            start_time = time.time()
            org_res = func(*args, **kw)
            end_time = time.time()  # 会先执行后面的print,但是执行print的时候这里还没有执行到(函数只是执行声明)
            return org_res
        return wrapper
    print(end_time-start_time)  # 会显示 name 'end_time' is not defined
    return decorator
  1. 常规装饰器
# 0 最简单的装饰器【这样写无法在函数后执行内容】
def wrap(func):   # 参数function,修饰函数
    return func   # 返回原来的函数

@wrap
def atest(str):
    print(str)

atest('hello')


# 1. 【def inner -> 返回 inner】
def wrapper(func): # 装饰器函数,func为被装饰函数   
        def inner(*args,**kwargs): # 被装饰后的函数可以的参数形式       
            """被装饰函数前需要添加的内容"""                 
            ret=func(*args,**kwargs) #被装饰函数        
            """被装饰函数后需要添加的内容"""        
            return ret   
        return inner


# 2. 带参数装饰器(方法1)
def out_wrapper(flag):     
         def wrapper(func): #装饰器函数,func为被装饰函数   
                def inner(*args,**kwargs):        
                        """被装饰函数前需要添加的内容"""                 
                        ret=func(*args,**kwargs) #被装饰函数        
                        """被装饰函数后需要添加的内容"""        
                        return ret   
                return inner
        return wrapper


# 3. 带参数装饰器(方法2)(采用类的方法)
class allow_count:
    def __init__(self, count):
        self.count = count
        self.i = 0

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            if self.i >= self.count:
                return
            self.i += 1
            return func(*args, **kw)
        return wrapper

  1. 类装饰器(装饰类的)

# 装饰器 【cls 参数 -> 返回 cls】
def attr_upper(cls):
    for attrname,value in cls.__dict__.items():
        if isinstance(value,str):
            if not value.startswith('__'):
                setattr(cls,attrname,bytes.decode(str.encode(value).upper()))
    return cls    


# 函数定义
@attr_upper
class Person:
    sex = 'man'

# 效果
print(Person.sex) # MAN

  1. 装饰器(装饰器是类)

# 原理:
# 【func参数 -> 返回 func的result】
# __call__ 方法可以将实例对象编程可调用对象
# 装饰器是一个语法糖相当于 func = cache(func)
# 凡是可以将 () 直接应用到自身并执行,都称为可调用对象。对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写

class CLanguage:
    def __call__(self, *args, **kwargs):
        print('__call__ :',args,kwargs)

c = CLanguage()
c('11','22','33',d='44')



# 实际应用1 (缓存装饰器)
class Cache:
    def __init__(self, func):
        self.func = func
        self.data = {}     # 很巧妙,不init就不会执行这里的代码

    def __call__(self, *args, **kwargs):
        func = self.func
        data = self.data
        key = f'{func.__name__}-{str(args)}-{str(kwargs)})'
        if key in data:
            result = data.get(key)
            print('cached')
        else:
            result = func(*args, **kwargs)
            data[key] = result
            print('calculated')
        return result

@Cache
def rectangle_area(length, width):
    return length * width

rectangle_area(2, 3)
# calculated
# 6

rectangle_area(2, 3)
# cached
# 6



# 实际应用2 (限制执行次数)
import functools

class allow_count:
    def __init__(self, count):
        self.count = count
        self.i = 0

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            if self.i >= self.count:
                return
            self.i += 1
            return func(*args, **kw)
        return wrapper


@allow_count(3)
def job(x):
    x += 1
    return x


for i in range(5):
    print(job(i))
# 结果:
#
# 1
# 2
# 3
# None
# None




# 实际应用3 (robot中)(这里没有使用__call__的原理,使用了描述符类的概念)
class setter(object):

    def __init__(self, method):
        self.method = method
        self.attr_name = '_setter__' + method.__name__
        self.__doc__ = method.__doc__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        try:
            return getattr(instance, self.attr_name)
        except AttributeError:
            raise AttributeError(self.method.__name__)

    def __set__(self, instance, value):
        if instance is None:
            return
        setattr(instance, self.attr_name, self.method(instance, value))



@setter
def keywords(self, keywords):
    """Suite setup and teardown as a :class:`~.Keywords` object."""
    return Keywords(self.keyword_class, self, keywords)

  1. 类写的装饰器来装饰类的方法
    https://zhuanlan.zhihu.com/p/44667584

# 要用类写的装饰器来装饰类的方法,只需要把可调用对象包装成函数就行。


# 定义一个简单的装饰器,什么也不做,仅仅是把可调用对象包装成函数
def method(call):
    def wrapper(*args, **kwargs):
        return call(*args, **kwargs)
    return wrapper

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
    
    @method
    @Cache
    def area(self):
        return self.length * self.width

r = Rectangle(2, 3)
r.area()
# calculated
# 6

r.area()
# cached
# 6
或者用@property还能直接把方法变成属性。

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
    
    @property
    @Cache
    def area(self):
        return self.length * self.width

r = Rectangle(2, 3)
r.area
# calculated
# 6

r.area
# cached
# 6

其他人写的博客:
https://blog.csdn.net/lht0909/article/details/100715749

posted @ 2020-07-08 11:39  该显示昵称已被使用了  阅读(145)  评论(0编辑  收藏  举报