阅读 39

py装饰器的6种写法

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

装饰器

装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。

场景:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了它就可以抽离出大量与函数功能无关的雷同代码并继续重用。
复制代码
装饰器的使用方法很固定
  • 先定义一个装饰器(帽子)
  • 再定义你的业务函数或者类(人)
  • 最后把这个装饰器(帽子)扣在这个函数(人)头上

# 定义装饰器
def decorator(func):
    def wrapper(*args, **kw):
        return func()
    return wrapper
    
# 定义业务函数并进行装饰
@decorator
def function():
    print("hello, decorator")
    

实际上装饰器并不是编码必须性,也就是你不使用装饰器完全可以,它的出现使我们的代码有如下优点: 
- 更加优雅,代码结构更加清晰
- 将实现特定的功能代码封装成装饰器,提高代码复用率,增强代码可读性
复制代码
1. 普通装饰器
最普通的装饰器,实现的功能:
- 在函数执行前,先打印一行日志
- 在函数执行完,再记录一行日志

# 这是装饰器函数,参数func是被装饰的函数
def logger(func):
    def wrapper(*args, **kw):
        print("开始执行:{}函数".format(func.__name__))
        
        # 真正执行的是这行
        func(*args, **kw)
        print("执行完成")
    return wrapper
    
@logger
def add(x,y):
    print('{} + {} = {}'.format(x, y, x+y))
复制代码
#执行函数
add(10, 30)
#输出
开始执行:add函数
10 + 30 = 40
执行完成
复制代码
2. 带参数的函数装饰器
不传参的装饰器,只能对被装饰函数执行固定逻辑。
这也意味着如果装饰器的逻辑代码的执行需要根据不同场景进行调整,需要写多个装饰器。

def say_hello(contry):
    def wrapper(func):
        def deco(*args, **kwargs):
            if contry == "china":
                print("你好!")
            elif contry == "america":
                print('hello.')
            else:
                return

            # 真正执行函数的地方
            func(*args, **kwargs)
        return deco
    return wrapper
    
# 小明,中国人
@say_hello("china")
def xiaoming():
    pass

# jack,美国人
@say_hello("america")
def jack():
    pass
复制代码
# 执行
xiaoming()
print("------------")
jack()
# 输出
你好!
------------
hello.
复制代码
3. 不带参数的装饰器
基于类实现的装饰器
必须实现 __call__ 和 __init__ 两个内置函数
__call__:接收被装饰函数
__init__:实现装饰逻辑

# 以打印日志为例
class logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logger
def say(something):
    print("say {}!".format(something))

say("hello")

# 执行输出
[INFO]: the function say() is running...
say hello!
复制代码
4. 带参数的装饰器
上面的只能打印INFO级别的日志,我们还需要打印其他如DEBUG、WARNING等级别日志。这就需要给类装饰器传入参数,给这个函数指定级别。
带参数和不带参数的类装饰器有很大不同。
__init__:不再接收被装饰函数,而是接收传入参数。
__call__:接收被装饰函数,实现装饰逻辑。

class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")

# 执行输出
[WARNING]: the function say() is running...
say hello!
复制代码
5. 使用偏函数与类实现装饰器
决大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。
python对某个对象是否能通过装饰器( @decorator )形式使用只有一个要求:decorator必须是一个“可被调用(callable)的对象”

DelayFunc 是一个实现了 __call__ 的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器。

import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

    def eager_call(self, *args, **kwargs):
        print('Call without delay')
        return self.func(*args, **kwargs)

def delay(duration):
    """
    装饰器:推迟某个函数的执行。
    同时提供 .eager_call 方法立即执行
    """
    # 此处为了避免定义额外函数,
    # 直接使用 functools.partial 帮助构造 DelayFunc 实例
    return functools.partial(DelayFunc, duration)
    
@delay(duration=2)
def add(a, b):
    return a+b
    
# 执行输出
>>> add    # 可见 add 变成了 Delay 的实例
<__main__.DelayFunc object at 0x107bd0be0>
>>> 
>>> add(3,5)  # 直接调用实例,进入 __call__
Wait for 2 seconds...
8
>>> 
>>> add.func # 实现实例方法
<function add at 0x107bef1e0>
复制代码
6. 能装饰类的装饰器
装饰器版的单例写法
instances = {}

def singleton(cls):
    def get_instance(*args, **kw):
        cls_name = cls.__name__
        print('===== 1 ====')
        if not cls_name in instances:
            print('===== 2 ====')
            instance = cls(*args, **kw)
            instances[cls_name] = instance
        return instances[cls_name]
    return get_instance

@singleton
class User:
    _instance = None

    def __init__(self, name):
        print('===== 3 ====')
        self.name = name
复制代码
文章分类
后端
文章标签