Python中的各种装饰器解析

 更新时间:2023年11月03日 09:55:55   作者:惊瑟  
这篇文章主要介绍了Python中的各种装饰器解析,Python装饰器可以在不改变函数原实现方式的前提下,为函数添加额外的功能,需要的朋友可以参考下

前言

Python装饰器可以在不改变函数原实现方式的前提下,为函数添加额外的功能。装饰器的功能并不费解(func = decorator(func) ),但具体实现时有一些细节还是需要搞明白。

一、函数装饰器

为表述方便,下面例子decorator开头的表示装饰器函数,func表示被装饰函数。

decorator不带参数

#不带参数的装饰器

def decorator_without_args(func):
    def wrapper(msg):
        print('I got a decorator')
        return func(msg)
    return wrapper

@decorator_without_args
def func(msg):
    print(f'msg is : {msg}')
    
func('hello world')

输出如下:

I got a decorator
msg is : hello world

上面就是最简单的装饰器,值得注意的是:

  • 不带参的decorator装饰函数时,勿带括号@decorator_without_args
  • 装饰器外层函数的return不加括号,如return wrapper
  • func本身传给外层的函数decorator_without_args(),而func的参数msg则传给内层函数wrapper()
  • func本身def func(msg),与def wrapper(msg)以及wrapper中return func(msg)的参数个数必须保持一致,不一致就会报错。这个特性可以理解为装饰器的内层函数(wrapper)有两个使命:首先是添加额外的功能,其次是将被装饰函数(func)的参数msg接收过来,以便最后rerun func(msg)。而第二点其实和装饰器本身想实现的功能是无关的,所以为了有更好的通用性,我们可以将装饰器改造为以下形式,这也是日常所见到的最普遍的形式:
def decorator_without_args(func):
    def wrapper(*args, **kwargs):
        print('I got a decorator')
        return func(*args, **kwargs)
    return wrapper

decorator带参数

实际项目中,除了func本身需要传参外,有时候decorator也需要传入参数,此时写法略有不同:

#带参数的装饰器

def decorator_with_args(flag):
    def decorator(func):
        def wrapper(*agrs, **kwargs):
            if flag:
                print('flag is True')
            else:
                print('flag is False')
            return func(*agrs, **kwargs)
        return wrapper
    return decorator

@decorator_with_args(True)
def func(msg):
    print(f'msg is : {msg}')

func('hello world')

值得注意的是:

  • 以上示例相当于func = decorator_with_args(flag)(func)
  • 带参的decorator装饰函数时,请加上括号并传入需要的参数 @decorator_with_args(True)
  • 一共有三层,最外层用于接收装饰器函数的参数,内层和不带参的装饰器一样。因此可以得出结论:若装饰器函数带参,需要写成三层,最外层用于接收装饰器函数的参数,中间一层接受被装饰函数func,最里面一层用于接收被装饰函数func的参数(*args, **kwargs)

二、类的装饰器

表示在类上使用的装饰器,类是被装饰对象,请区分“类装饰器”,举个最经典的例子—装饰器实现单例模式:

# 类的装饰器

def decorator_singleton(cls):
    def wrapper(*args, **kwargs):
        if not hasattr(cls, '_instance'):
            print('I am created firstly.')
            cls._instance = cls(*args, **kwargs)
        else:
            print('I have been created,so I am just an old instance!!!')
        return cls._instance
    return wrapper

@decorator_singleton
class Register:
    pass

a = Register()
b = Register()
id_a = id(a)
id_b = id(b)

print(f'a id is:{id_a} \nb id is:{id_b}')

输出如下:

I am created firstly.
I have been created,so I am just an old instance!!!
a id is:140077188368368 
b id is:140077188368368

可以看出来,单例装饰器起到了作用,执行过程可以这么表示:

Register = decorator_singleton(Register)

相当于将类本身作为参数传给装饰器,最后装饰器再返回一个实例对象。事实上,把Register视作一个函数就很好理解了,毕竟Register实例化时,也要执行Register()。

三、类装饰器

表示类作为装饰器,此时装饰器是用类实现的,与“类的装饰器”做区分。

# 类装饰器

class Decorator:

    def __init__(self, func):
        print('I am initialing')
        self.func = func

    def __call__(self, *args, **kwargs):
        print('I am called')
        return self.func(*args, **kwargs)
    
@Decorator
def func(msg):
    print(f'msg is {msg}')

func('hello world')

输出:

I am initialing
I am called
msg is hello world

如果明白了装饰器的原理,类装饰器也很好理解。事实上,任何类型装饰器,要起到作用,有两个隐式前提:

  • 装饰器本身是一个可调用对象(callable),毕竟本质上绕不开func = decorator(func)
  • 必须向装饰器传入两类参数:一是func本身,而是func的参数(*args, **kwargs)

在Python中,对于类对象class,只要实现了__call__()方法,该类实例化后就可以像调用函数一样使用这个实例。上面示例中,类Decorator实例化后可以像函数一样被调用,这就满足了装饰器第一个前提。 对于第二个前提,众所周知,类的初始化变量可以通过__init__()接收,因此,通过init将func传入类中,而func中参数则被传到了__call__()中。类的实例化过程想必大家也都知道,总是先执行init(),因此I am initialing最先被打印出来。此处小伙伴可能有疑问了,可不可以将func和func中参数全都传给__call__(),从而省去__init__()呢?或者说,可不可以将func和func中的参数全都传给__init()__而省去__call__()呢?答案是不可以。事实上,装饰的过程分为两步:

func = decorator(func)
func(args)

两类参数(被装饰函数本身,及其参数)也是分两次传入装饰器的。对于类装饰器,第一步其实是实例化类装饰器,所以func只能传给__init__()。而func的args则必须传给__call__(),至于为什么,请看下面结论。此处有没有似曾相识的感觉?其实跟函数装饰器func要传给外层函数,args传给内层函数一个道理。

三、结论

坚持看完的小伙伴下次一定对装饰器的原理了然于胸了。而且可能还有额外的收获,比如类装饰器的原理其实跟类实现了__call__()就可以像函数那样调用的原理一样,来看例子:

class CallTest:
    def __init__(self):
        pass
    def __call__(self, msg):
        print(msg)

ct = CallTest()
ct('I am called')

输出:

I am called

这个例子可以明显看出来,类在实例化出对象后,对象的参数其实就是传给了__call__()了。一言以蔽之,Python的()运算符其实就是__call__(),再来看上面类装饰器装饰的过程:

func = decorator(func)
func(args)

第二步func(args)本质上就是func.__call__(agrs),所以agrs只能传给__call__()。

到此这篇关于Python中的各种装饰器解析的文章就介绍到这了,更多相关Python装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决python父线程关闭后子线程不关闭问题

    解决python父线程关闭后子线程不关闭问题

    这篇文章主要介绍了解决python父线程关闭后子线程不关闭问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • pytorch中.numpy()、.item()、.cpu()、.detach()以及.data的使用方法

    pytorch中.numpy()、.item()、.cpu()、.detach()以及.data的使用方法

    这篇文章主要给大家介绍了关于pytorch中.numpy()、.item()、.cpu()、.detach()以及.data的使用方法,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-08-08
  • Pytes正确的配置使用日志功能

    Pytes正确的配置使用日志功能

    在pytest自动化测试中,如果只是简单的从应用的角度来说,完全可以不去了解pytest中的显示信息的部分以及原理,可以通过使用推荐的pytest.ini配置,从而可以做到相对来说比较通用的日志配置,这篇文章主要介绍了Pytes如何正确的配置使用日志功能,需要的朋友可以参考下
    2022-12-12
  • pandas的to_datetime时间转换使用及学习心得

    pandas的to_datetime时间转换使用及学习心得

    这篇文章主要给大家介绍了关于pandas的to_datetime时间转换使用及学习心得的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用pandas具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08
  • 跟老齐学Python之正规地说一句话

    跟老齐学Python之正规地说一句话

    虽然在第一部分中,已经零星涉及到语句问题,并且在不同场合也进行了一些应用。毕竟不那么系统。本部分,就比较系统地介绍python中的语句。
    2014-09-09
  • Pandas绘图函数超详细讲解

    Pandas绘图函数超详细讲解

    matplotlib要组装一张图表,需要的各个基础组件对象。相对工作量较大,但在pandas中我们有行标签和列标签以及分组信息。原本制作一张图表需要一大堆matplotlib代码。在pandas中只需要一两条代码就可以了,今天记录一下,pandas中常见的几个绘制图表的方法
    2022-12-12
  • Python类的动态修改的实例方法

    Python类的动态修改的实例方法

    这篇文章主要介绍了Python类的动态修改的实例方法的相关资料,需要的朋友可以参考下
    2017-03-03
  • Python 虚拟空间的使用代码详解

    Python 虚拟空间的使用代码详解

    这篇文章主要介绍了Python 虚拟空间的使用,本文通过示例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-06-06
  • Python爬虫基于lxml解决数据编码乱码问题

    Python爬虫基于lxml解决数据编码乱码问题

    这篇文章主要介绍了Python爬虫基于lxml解决数据编码乱码问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Python实现获取照片拍摄日期并重命名的方法

    Python实现获取照片拍摄日期并重命名的方法

    这篇文章主要介绍了Python实现获取照片拍摄日期并重命名的方法,涉及Python针对文件属性及文件名相关操作技巧,需要的朋友可以参考下
    2017-09-09

最新评论