前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python装饰器的使用方法

python装饰器的使用方法

作者头像
编程黑洞
发布2023-03-06 19:29:51
3200
发布2023-03-06 19:29:51
举报
文章被收录于专栏:编程黑洞编程黑洞

# 0. 前言

装饰器在 python 中使用的频率非常高,它可以在不改动原有函数的基础上对其进行增强功能。

下面主要是介绍装饰器的各种用法,并理解其运行过程。

# 1. 使用

# 1.1 在函数上添加装饰器

decro 是一个装饰器函数,其实现是将内部的函数 wrapper 作为返回值返回出去。

在函数 test 上添加 @decro 进行使用,可以将本函数作为一个参数传入到 decro 函数中,然后,然后得到的是装饰器函数内部返回的函数 wrapper, 我们在调用 test 方法时,其实调用的是装饰器返回的 wrapper 函数,该函数中会调用被装饰的函数 test

代码语言:javascript
复制
def decro(func):  
    def wrapper(*args, **kwargs):  
        print("wrapper")  
        return func()  
  
    return wrapper


@decro  
def test():  
    print("test")  


test()

输出:

代码语言:javascript
复制
wrapper
test

# 1.2 装饰器的运行过程

装饰器时在被装饰的函数定义之后立即运行的,当执行到@decro 装饰 test 函数时,会马上执行函数 decro,然后将 wrapper 给返回出去。

代码语言:javascript
复制
def decro(func):  
    print("decro")  
  
    def wrapper(*args, **kwargs):  
        print("wrapper")  
        return func()  
  
    return wrapper  
  
print("start")  
  
@decro  
def test():  
    print("test")  
  
print("end")

test()

输出如下,可以看到先执行了 start,然后马上执行了装饰器 decro,然后再执行 end,当我们调用 test 函数时,执行了装饰器内部函数 wrapper,然后再调用被装饰的函数 test

代码语言:javascript
复制
start
decro
end
wrapper
test

# 1.3 保存原函数信息

在使用装饰器时,调用的原方法已经被替换为装饰器返回的新方法了,所以方法的元信息已经被替换了, 通过 name、doc 得到的元数据已经被替换成了新方法的。

代码语言:javascript
复制
def decro(func):  
    def wrapper(*args, **kwargs):  
        """ wrapper doc """  
        print("wrapper")  
        return func(*args, **kwargs)  
  
    return wrapper  
  
  
@decro  
def test():  
    """ test doc """  
    print("test")  
  
  
print("test's __name__ = {}".format(test.__name__))  
print("test's __doc__ = {}".format(test.__doc__))

输出如下,会发现函数 test 的函数信息 __name____doc__ 变成 wrapper 的信息。

代码语言:javascript
复制
test's __name__ = wrapper
test's __doc__ =  wrapper doc

但是我们不想要改变原方法的元信息,这个时候需要使用 functools.wraps 解决。

代码语言:javascript
复制
from functools import wraps  
  
def decro(func):  
  
    @wraps(func)  
    def wrapper(*args, **kwargs):  
        """ wrapper doc """  
        print("wrapper")  
        return func(*args, **kwargs)  
  
    return wrapper  
  
  
@decro  
def test():  
    """ test doc """  
    print("test")  
  
  
print("test's __name__ = {}".format(test.__name__))  
print("test's __doc__ = {}".format(test.__doc__))

输出如下,会发现,test 函数信息没有被替换掉,保证了函数的原汁原味。

代码语言:javascript
复制
test's __name__ = test
test's __doc__ =  test doc 

# 1.4 调用原函数

装饰器可以增强函数的功能,但是在某些场景我就想要使用原函数,而不想使用装饰之后的函数,可以通过调用__wrapped__来调用原函数。

代码语言:javascript
复制
from functools import wraps  
  

def decro(func):  
    @wraps(func)  
    def wrapper(*args, **kwargs):  
        """ wrapper doc """  
        print("wrapper")  
        return func(*args, **kwargs)  
  
    return wrapper  
  
  
@decro  
def test():  
    """ test doc """  
    print("test")  
  
  
test.__wrapped__()

输出如下,输出 text,而没有输出 wrapper,说明调用的是原函数。

代码语言:javascript
复制
test

# 1.5 带参数的装饰器

还有这么一种场景,我们想要在装饰器中添加参数。

想要通过参数决定日志级别,这里的 logged 接收 level 参数并将它作用在内部函数中,返回值是将 decro 函数返回,然后再将函数 test 作为参数从传入到 decro 函数中,再将 wrapper 返回,最终 test 还是替换成了 wrapper,在该方法中使用了传入的 ERROR 的日志级别。

代码语言:javascript
复制
from functools import wraps  
import logging  
  
  
def logged(level):  
    def decro(func):  
        @wraps(func)  
        def wrapper(*args, **kwargs):  
            """ wrapper doc """  
            logging.log(level, func.__name__)  
            return func(*args, **kwargs)  
  
        return wrapper  
  
    return decro  
  
  
@logged(level=logging.ERROR)  
def test():  
    """ test doc """  
    print("test")  
  
  
test()

输出了 ERROR 日志级别的日志:

代码语言:javascript
复制
ERROR:root:test
test

# 1.6 带可选参数的装饰器

上面实现的装饰器是必须要带上参数的,但是有的时候,我们不需要带参数,那么该如何实现?

装饰器的 func 默认值为 None,当传入 level 参数时,则返回偏函数 partial ,该函数会基于 logged 创建一个仅包含 level 的新的函数,这个新的函数作为新的装饰器来装饰 add 函数。

当没有传入 level 参数时,就和普通的装饰器一样使用。

代码语言:javascript
复制
from functools import wraps, partial  
import logging  
  
  
def logged(func=None, level=logging.INFO):  
    if func is None:  
        return partial(logged, level=level)  
  
    @wraps(func)  
    def wrapper(*args, **kwargs):  
        logging.log(level, func.__name__)  
        return func(*args, **kwargs)  
  
    return wrapper  
  
  
@logged(level=logging.ERROR)  
def add(a, b):  
    print("add")  
    return a + b  
  
  
@logged  
def add2(a, b):  
    print("add2")  
    return a + b  
  
  
print(add(1, 2))  
print("-" * 10)  
print(add2(1, 2))

输出如下,add 函数的装饰器传入了日志级别为 ERROR 的参数,输出了 ERROR 的日志,而add2 没有。

代码语言:javascript
复制
ERROR:root:add
add
3
----------
add2
3

# 1.7 在类上添加装饰器

上面都是使用装饰器来增强函数的功能,但它还可以增强类的功能,对类进行装饰。

下面的例子中,decro 将被装饰的类 Demo 传入进来,主要是将其类中 __getattribute__ 方法替换成了 new_getattribute 方法。

代码语言:javascript
复制
def decro(cls):  
    orig_getattribute = cls.__getattribute__  
  
    def new_getattribute(self, name):  
        print("get name = {}".format(name))  
        return orig_getattribute(self, name)  
  
    cls.__getattribute__ = new_getattribute  
    return cls  
  
  
@decro  
class Demo:  
  
    def __init__(self, num):  
        self.num = num  
  
  
d = Demo(1)  
print(d.num)

输出如下,获取 num 的值时,调用了装饰器替换的 new_getattribute 方法。

代码语言:javascript
复制
get name = num
1

# 1.8 类装饰器

之前都是使用函数方法来定义装饰器,但其实也可以通过类来定义装饰器。

在类装饰器中定义__init__方法,被它装饰的函数会被传入到 func 参数中,这个时候该类装饰器已经被实例化了,也就是将该实例对象替换了被装饰的函数 say。

当我们调用 say 函数时,其实调用的是类装饰器的对象,这个时候会调用__call__方法,该方法中可以对原函数进行增强,并进行调用原方法。

代码语言:javascript
复制
class logger(object):  
    def __init__(self, func):  
        self.func = func  
  
    def __call__(self, *args, **kwargs):  
        print("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")

输出如下:

代码语言:javascript
复制
the function say() is running...
say hello!

# 1.9 暴露被装饰的元信息

这个时候会出现和函数装饰器一样的问题,那就是被装饰的函数的元信息已经被替换掉了,这个时候我们还是想保留原有的原信息。

还是使用 wraps 函数来解决该问题。

代码语言:javascript
复制
from functools import wraps  
  
  
class logger(object):  
    """ logger doc """  
  
    def __init__(self, func):  
        wraps(func)(self)  
  
    def __call__(self, *args, **kwargs):  
        print("the function {func}() is running...".format(func=self.__wrapped__.__name__))  
        return self.__wrapped__(*args, **kwargs)  
  
  
@logger  
def say(something):  
    """ say doc """  
    print("say {}!".format(something))  
  
  
say("hello2")  
print(say.__doc__)

输出的是 say 方法的 doc:

代码语言:javascript
复制
the function say() is running...
say hello2!
 say doc 

# 1.10 带参数的类装饰器

那么带参数的类装饰器该如何实现呢?

__init__ 方法中接收装饰器传入的参数,保存起来,然后再通过 __call__ 函数将内部函数 wrapper 给返回出去,这个时候被装饰的函数已经被 wrapper 给替换了。

代码语言:javascript
复制
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="ERROR")  
def say(something):  
    print("say {}!".format(something))  
  
  
say("hello2")

输出如下,调用 say 函数也就是调用 wrapper 函数:

代码语言:javascript
复制
[ERROR]: the function say() is running...
say hello2!

# 2. 总结

装饰器的用法很多,封装成库,给其他人使用也非常的方便,我们需要理解它的运行过程,才能更好的使用它。

# 3. 参考资料

  • https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p01_put_wrapper_around_function.html
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # 0. 前言
  • # 1. 使用
    • # 1.1 在函数上添加装饰器
      • # 1.2 装饰器的运行过程
        • # 1.3 保存原函数信息
          • # 1.4 调用原函数
            • # 1.5 带参数的装饰器
              • # 1.6 带可选参数的装饰器
                • # 1.7 在类上添加装饰器
                  • # 1.8 类装饰器
                    • # 1.9 暴露被装饰的元信息
                      • # 1.10 带参数的类装饰器
                      • # 2. 总结
                      • # 3. 参考资料
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档