【python3】装饰器的使用

主要是个人使用与理解,借鉴了网上其他大神的写法与思路

恶补了 Python 装饰器的八种写法,你随便问~
python装饰器带括号和参数

一、闭包

闭包相当于函数中,嵌套另一个函数,并返回

def outer_func(a):  # 外层函数
    def inner_func(b):  # 内层函数
        return a + b

    return inner_func  # 此处需要返回内层函数,才行行程闭包

func = outer_func(10)  # 将10传给outer_func,返回inner_func,并赋值给func
print(func(5))  # 将5传给func(即inner_func),并打印
# 结果为15

二、装饰器

装饰器本质上是一个闭包函数,它使用闭包的特性来记住并调用它装饰的函数
闭包传入的是变量,而装饰器利用闭包的原理,可以传入函数(在装饰器中,也可以把函数当作变量)。

任何可调用的对象都可以作为装饰器,只要它接受一个函数作为参数并返回一个函数。这通常意味着装饰器可以是:
函数:这是最常见的装饰器形式,它接受一个函数作为参数并返回一个新的函数。
类:类也可以用作装饰器,如果它们实现了__call__方法。在这种情况下,类的实例将是一个可调用对象,它可以接受一个函数作为参数,并在__call__方法中返回一个新的函数。
带有__call__方法的任何对象:任何实现了__call__方法的对象都可以被用作装饰器。这意味着对象在被像函数那样调用时(即使用括号)会执行__call__方法。

2.1 使用函数作为装饰器

下面定义了一个logger_printer,用于在函数执行前后在控制台打印输出,可以需要的函数前都加上装饰器。*args, **kwargs用于接收test_func的参数

def logger_printer(func):
    def wrapper(*args, **kwargs):
        print(f'【开始执行】:{func.__name__}方法!')
        func(*args, **kwargs)  # 此处执行被装饰的函数
        print(f'【结束执行】:{func.__name__}方法!')

    return wrapper

@logger_printer
def anohter_func():
    print('回到anohter_func方法')

@logger_printer
def test_func():
    print('回到test_func方法')
    anohter_func()

test_func()

在这里插入图片描述

同理,我们也可以做一个统计函数执行时间的装饰器

def time_calculator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        expend_time = end_time - start_time
        print(f'【耗时统计:】{func.__name__}方法耗时{expend_time}秒')

    return wrapper

@time_calculator
def test():
    print('回到test方法,开始等待5s')
    time.sleep(5)
    print('等待5s结束')

test()

在这里插入图片描述

2.2 传入参数的(函数)装饰器

上面2.1使用的装饰器没有传入参数,导致装饰器只能有固定的用法,当装饰器能够接收参数时,它的功能就非常灵活了。

制作一个延时功能的装饰器timer,再使用之前的time_calculator对其进行耗时统计
多个叠加的装饰器,其实就是上方的装饰器装饰了下方的装饰器,层层往下装饰,运行的顺序也是从上往下

def timer(delay_time: int):
    def wrapper(func):
        def inner(*args, **kwargs):
            print(f'开始等待【{delay_time}】秒')
            time.sleep(delay_time)
            print(f'结束等待【{delay_time}】秒')
            func(*args, **kwargs)  # 此处执行被装饰的函数

        return inner

    return wrapper

@time_calculator
@timer(2)
def test():
    print('回到test方法')

test()

在这里插入图片描述

这边可以看到“【耗时统计:】inner方法耗时2.002117872238159秒”,这边写的是inner方法,而不是我们想要装饰的test方法,或是退一步也应该写timer方法吧。

调试时可以发现time_calculator内传入的方法的确是inner。
在这里插入图片描述
通过在inner前增加wraps装饰器,即可以解决该问题。

from functools import wraps

def timer(delay_time: int):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            print(f'开始等待【{delay_time}】秒')
            time.sleep(delay_time)
            print(f'结束等待【{delay_time}】秒')
            func(*args, **kwargs)  # 此处执行被装饰的函数

        return inner

    return wrapper

在这里插入图片描述
在这里插入图片描述

如果涉及多个装饰器叠加,并要得到正确的函数名,需要在多个装饰内,增加wraps

如何理解wraps,见开头分享的文章,里面有wraps的介绍。

2.3 使用类作为装饰器

类作为装饰器,其用法实际和函数作为装饰器是差不多,我们把之前logger装饰器使用类再写一遍。

class Logger:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'【开始执行】:{self.func.__name__}方法!')
        self.func(*args, **kwargs)  # 此处执行被装饰的函数
        print(f'【结束执行】:{self.func.__name__}方法!')

@Logger
def test():
    print('回到test方法了')

test()

在这里插入图片描述

如果使用函数装饰器,与类装饰器叠加使用可能会有问题,如下:当类装饰器放在函数装饰器上时,会由于Logger类没有__name__属性引起AttributeError

class Logger:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'【开始执行】:{self.func.__name__}方法!')
        result = self.func(*args, **kwargs)  # 执行被装饰的函数并保存返回值
        print(f'【结束执行】:{self.func.__name__}方法!')
        return result  # 返回被装饰函数的返回值

def time_calculator(func):
	@wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)  # 执行被装饰的函数并保存返回值
        end_time = time.time()
        expend_time = end_time - start_time
        print(f'【耗时统计:】{func.__name__}方法耗时{expend_time}秒')
        return result  # 返回被装饰函数的返回值

    return wrapper

@time_calculator
@Logger
def test():
    print('回到test方法了')
    time.sleep(3)

test()

在这里插入图片描述
两种解决方法:1、将Logger与time_calculator上下交换下位置,不过内部的逻辑可能需要修改;2、在Logger内加入__getattr__方法用于返回不存在的对象属性。

class Logger:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'【开始执行】:{self.func.__name__}方法!')
        result = self.func(*args, **kwargs)  # 执行被装饰的函数并保存返回值
        print(f'【结束执行】:{self.func.__name__}方法!')
        return result  # 返回被装饰函数的返回值

    def __getattr__(self, item):
        return getattr(self.func, item)

在这里插入图片描述

2.4 使用带参数的类作为装饰器

在2.3的Logger基础上再加点传入日志级别的数据
由于 Logger 类正确地在 wrapper 函数中调用了原始函数 func,并且没有修改或覆盖任何与函数名称相关的属性,因此它不会干扰到外部装饰器( time_calculator)对函数名称的访问,所以不会报错。

class LogLevel(Enum):
    DEBUG = 1
    INFO = 2
    WARNING = 3
    ERROR = 4

class Logger:
    def __init__(self, level: LogLevel):
        self.level = level

    def __call__(self, func):
    	@wraps(func)
        def wrapper(*args, **kwargs):
            print(f'【{self.level.name}】:开始运行【{func.__name__}】')
            func(*args, **kwargs)
            print(f'【{self.level.name}】:结束运行【{func.__name__}】')

        return wrapper

@time_calculator
@Logger(LogLevel.INFO)
def test():
	time.sleep(3)
    print('回到test方法了')

test()

在这里插入图片描述

2.5 可以装饰类的装饰器

常用的就是单例模式了singleton

def singleton(cls):
    instance = {}

    def get_instance(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]

    return get_instance

@singleton
class Abc:
    pass

a1 = Abc()
a2 = Abc()
print(id(a1), id(a2))

就这样吧,装饰器用得太复杂,反而背离了初衷

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值