Python-生成器

理解装饰器

装饰器是Python里面经常使用到的一个功能,通过装饰器我们能在不修改函数的情况下为函数增加一些功能,装饰器通过语法糖调用

装饰器

普通装饰器

def logger(func):
    def decorator(*args, **kwargs):
        print("func start")
        func(*args, **kwargs)
    return decorator

@logger
def func(name):
    print("my name is %s" % name)


func("leon")

这是个装饰器的简单例子,整个调用其实就相当于logger(func(name)), 如果是有多个装饰的情况下,那么调用顺序是怎样的?

@A
@B
@C
def func():
    pass

上面执行的的过程就是A(B(C(func())))

wraps

我们先看一个例子

def logger(func):
    def decorator(*args, **kwargs):
        print("func start")
        func(*args, **kwargs)
    return decorator

@logger
def func(name):
    print("my name is %s" % name)


func("leon")
print(func.__name__)   # decorator

当我们调用func.name 原本上应该输出func,但是因为加入了装饰器,导致__name__、__doc__等一些构造函数被改变了,那么如果我们如何解决这个问题呢? 这里就需要用到functools.wraps,我们看下加入wraps试下

from functools import wraps
def logger(func):
    @wraps(func)
    def decorator(*args, **kwargs):
        print("func start")
        func(*args, **kwargs)
    return decorator

@logger
def func(name):
    print("my name is %s" % name)


func("leon")
print(func.__name__)    # func

这里__name__是我们预期的,wraps到底做了些什么事情,这里我们看下源码

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

       Returns a decorator that invokes update_wrapper() with the decorated
       function as the wrapper argument and the arguments to wraps() as the
       remaining arguments. Default arguments are as for update_wrapper().
       This is a convenience function to simplify applying partial() to
       update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

从源码上看wraps最终是调用了update_wrapper, 这里会将___name__,__doc__等一些构造函数从原函数赋值到wrapped的的函数中

带参数的装饰器

如果我们想给装饰器加一些参数呢,这个要怎么做?

from functools import wraps

def need_logger(need=True):
    def logger(func):
        @wraps(func)
        def decorator(*args, **kwargs):
            if need:
                print("func start")
            func(*args, **kwargs)
        return decorator
    return logger

@need_logger(need=True)
def func(name):
    print("my name is %s" % name)


func("leon")

如果装饰器要带参数的话,我们需要再把装饰器加一层,这个是函数的,我们也可以用类实现带参数的装饰器

class CustomLogger():
    def __init__(self, need=True):
        self.need = need

    def __call__(self, func):
        @wraps(func)
        def decorator(*args, **kwargs):
            if self.need:
                print("func start 2")
            func(*args, **kwargs)
        return decorator


@CustomLogger(need=True)
def func(name):
    print("my name is %s" % name)

第二种方法是不是有点不一样的感觉,通过类,我们可以将装饰器封装成一个完成包,去集成我们自己的功能

内置的三个装饰器

这里我们讲一下Python里面内置的装饰器

property

通过@property我们可以将一个函数变成一个属性进行调用,很大程度的方便了我们使用

class Demo():
    def __init__(self):
        self.value = 0

    @property
    def get_value(self):
        return self.value
    
    @get_value.setter
    def set_value(self, value):
        self.value = value


d = Demo()
print(d.get_value)   # 0

d.set_value = 1

print(d.get_value)   # 1

classmethod

classmethod和类绑定,不是和实例绑定, classmethod接收一个类参数cls, 可以修改类的状态,并将其作用到所有的类实例上

class Demo():
    value = 1

    @classmethod
    def get_value2(cls):
        cls.value += 1
        return cls.value


print(Demo.get_value2())  # 2
print(Demo.value)    # 2

可以看到,我们通过类方法修改的值已经作用域所有的类实例了

staticmethod

静态方法跟普通函数没什么区别,通过类和实例都可以直接该函数

class Demo():
    @staticmethod
    def get_value():
        return "ok"

print(Demo.get_value())  # ok

2021-06-27 09:58 +0800