Back to Blogs
python
decorator
closure

Python Decorator and Closure

Soloman
2019-10-03

Python Decorator and Closure

1 Python Decorator

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

# 给business_func添加其它功能和打印日志信息

def loglog(func):
    def wrapper():
        print(f'Do something before run {func.__name__}...')
        ret = func()
        print(f'Do something after run {func.__name__}...')
        return ret
    return wrapper


@loglog
def business_func():
    print('run business function...')


if __name__ == '__main__':
    business_func()


Do something before run business_func...
run business function...
Do something after run business_func...

被装饰函数带有参数的情况

def loglog(func):
    def wrapper(*args, **kwargs):
        print(f'Do something before run {func.__name__}...')
        ret = func(*args, **kwargs)
        print(f'Do something after run {func.__name__}...')
        return ret
    return wrapper


@loglog
def business_func(who):
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


Do something before run business_func...
Soloman is running business function...
Do something after run business_func...
Soloman

decorator本身需要传入参数

def loglog(level):
    def outer_wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print(f'Do something {level} before run {func.__name__}...')
            ret = func(*args, **kwargs)
            print(f'Do something {level} after run {func.__name__}...')
            return ret
        return inner_wrapper
    return outer_wrapper


@loglog('important')
def business_func(who):
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


Do something important before run business_func...
Soloman is running business function...
Do something important after run business_func...
Soloman

装饰器极大地复用了代码,但有一个缺点就是原函数的元信息不见了,比如函数的 __module__, __name__, __qualname__, __doc__, __annotations__ 等。所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的

from functools import wraps


def loglog(level):
    def outer_wrapper(func):
        # @wraps(func)
        def inner_wrapper(*args, **kwargs):
            """This is wrapper function"""
            print(f'Do something {level} before run {func.__name__}...')
            ret = func(*args, **kwargs)
            print(f'Do something {level} after run {func.__name__}...')
            return ret
        return inner_wrapper
    return outer_wrapper


@loglog('important')
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


# 没有使用 functools.wraps(func)
Do something important before run business_func...
func name: inner_wrapper
func doc: This is wrapper function
Soloman is running business function...
Do something important after run business_func...
Soloman

# 使用 functools.wraps(func)
Do something important before run business_func...
func name: business_func
func doc: This is main business function
Soloman is running business function...
Do something important after run business_func...
Soloman

2 最终形态

2.1 不带参数的装饰器函数

from functools import wraps


def loglog(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f'{func.__name__} is running...')
        return func(*args, **kwargs)
    return wrapper


@loglog
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


business_func is running...
func name: business_func
func doc: This is main business function
Soloman is running business function...
Soloman

2.2 不带参数的装饰器类

from functools import wraps


class loglog:
    def __init__(self):
        pass

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f'{func.__name__} is running...')
            return func(*args, **kwargs)
        return wrapper


@loglog()
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


business_func is running...
func name: business_func
func doc: This is main business function
Soloman is running business function...
Soloman

2.3 带参数的装饰器函数

from functools import wraps


def loglog(level):
    def outer_wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print(f'{func.__name__} is running in level: {level}')
            return func(*args, **kwargs)
        return inner_wrapper
    return outer_wrapper


@loglog('important')
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


business_func is running in level: important
func name: business_func
func doc: This is main business function
Soloman is running business function...
Soloman

2.4 带参数的装饰器类

from functools import wraps


class loglog:
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f'{func.__name__} is running in level: {self.level}')
            return func(*args, **kwargs)
        return wrapper


@loglog('important')
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


business_func is running in level: important
func name: business_func
func doc: This is main business function
Soloman is running business function...
Soloman

2.5 定义子类扩展功能

from functools import wraps


class loglog:
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f'{func.__name__} is running in level: {self.level}')
            self.notify()
            return func(*args, **kwargs)
        return wrapper

    def notify(self):
        pass


class email_loglog(loglog):
    def __init__(self, level, receiver='admin@soloman.vip'):
        self._email = receiver
        super(email_loglog, self).__init__(level)

    def notify(self):
        print(f'Sending email to {self._email}')


@email_loglog('important')
def business_func(who):
    """This is main business function"""
    print(f'func name: {business_func.__name__}')
    print(f'func doc: {business_func.__doc__}')
    print(f'{who} is running business function...')
    return who


if __name__ == '__main__':
    res = business_func('Soloman')
    print(res)


business_func is running in level: important
Sending email to admin@soloman.vip
func name: business_func
func doc: This is main business function
Soloman is running business function...
Soloman