python装饰器的使用方法

2023-05-16

0. 前言

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

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

1. 使用

1.1 在函数上添加装饰器

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

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

def decro(func):  
    def wrapper(*args, **kwargs):  
        print("wrapper")  
        return func()  
  
    return wrapper


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


test()

输出:

wrapper
test

1.2 装饰器的运行过程

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

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

start
decro
end
wrapper
test

1.3 保存原函数信息

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

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 的信息。

test's __name__ = wrapper
test's __doc__ =  wrapper doc

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

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 函数信息没有被替换掉,保证了函数的原汁原味。

test's __name__ = test
test's __doc__ =  test doc 

1.4 调用原函数

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

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,说明调用的是原函数。

test

1.5 带参数的装饰器

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

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

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 日志级别的日志:

ERROR:root:test
test

1.6 带可选参数的装饰器

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

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

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

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 没有。

ERROR:root:add
add
3
----------
add2
3

1.7 在类上添加装饰器

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

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

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 方法。

get name = num
1

1.8 类装饰器

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

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

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

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")

输出如下:

the function say() is running...
say hello!

1.9 暴露被装饰的元信息

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

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

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:

the function say() is running...
say hello2!
 say doc 

1.10 带参数的类装饰器

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

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

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 函数:

[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

欢迎关注,互相学习,共同进步~

我的个人博客
公众号:编程黑洞

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python装饰器的使用方法 的相关文章

  • 使用 pythonbrew 编译 Python 3.2 和 2.7 时出现问题

    我正在尝试使用构建多个版本的 python蟒蛇酿造 http pypi python org pypi pythonbrew 0 7 3 但我遇到了一些测试失败 这是在运行的虚拟机上 Ubuntu 8 04 32 位 当我使用时会发生这种情
  • Python 的键盘中断不会中止 Rust 函数 (PyO3)

    我有一个使用 PyO3 用 Rust 编写的 Python 库 它涉及一些昂贵的计算 单个函数调用最多需要 10 分钟 从 Python 调用时如何中止执行 Ctrl C 好像只有执行结束后才会处理 所以本质上没什么用 最小可重现示例 Ca
  • SQLAlchemy 通过关联对象声明式多对多自连接

    我有一个用户表和一个朋友表 它将用户映射到其他用户 因为每个用户可以有很多朋友 这个关系显然是对称的 如果用户A是用户B的朋友 那么用户B也是用户A的朋友 我只存储这个关系一次 除了两个用户 ID 之外 Friends 表还有其他字段 因此
  • 如何在flask中使用g.user全局

    据我了解 Flask 中的 g 变量 它应该为我提供一个全局位置来存储数据 例如登录后保存当前用户 它是否正确 我希望我的导航在登录后在整个网站上显示我的用户名 我的观点包含 from Flask import g among other
  • 为 Anaconda Python 安装 psycopg2

    我有 Anaconda Python 3 4 但是每当我运行旧代码时 我都会通过输入 source activate python2 切换到 Anaconda Python 2 7 我的问题是我为 Anaconda Python 3 4 安
  • Django:按钮链接

    我是一名 Django 新手用户 尝试创建一个按钮 单击该按钮会链接到我网站中的另一个页面 我尝试了一些不同的例子 但似乎没有一个对我有用 举个例子 为什么这不起作用
  • 使用 matplotlib 绘制时间序列数据并仅在年初显示年份

    rcParams date autoformatter month b n Y 我正在使用 matpltolib 来绘制时间序列 如果我按上述方式设置 rcParams 则生成的图会在每个刻度处标记月份名称和年份 我怎样才能将其设置为仅在每
  • Flask 会话变量

    我正在用 Flask 编写一个小型网络应用程序 当两个用户 在同一网络下 尝试使用应用程序时 我遇到会话变量问题 这是代码 import os from flask import Flask request render template
  • 如何在 Python 中检索 for 循环中的剩余项目?

    我有一个简单的 for 循环迭代项目列表 在某些时候 我知道它会破裂 我该如何退回剩余的物品 for i in a b c d e f g try some func i except return remaining items if s
  • 根据列值突出显示数据框中的行?

    假设我有这样的数据框 col1 col2 col3 col4 0 A A 1 pass 2 1 A A 2 pass 4 2 A A 1 fail 4 3 A A 1 fail 5 4 A A 1 pass 3 5 A A 2 fail 2
  • 测试 python Counter 是否包含在另一个 Counter 中

    如何测试是否是pythonCounter https docs python org 2 library collections html collections Counter is 包含在另一个中使用以下定义 柜台a包含在计数器中b当且
  • OpenCV 无法从 MacBook Pro iSight 捕获

    几天后 我无法再从 opencv 应用程序内部打开我的 iSight 相机 cap cv2 VideoCapture 0 返回 并且cap isOpened 回报true 然而 cap grab 刚刚返回false 有任何想法吗 示例代码
  • 如何加速Python中的N维区间树?

    考虑以下问题 给定一组n间隔和一组m浮点数 对于每个浮点数 确定包含该浮点数的区间子集 这个问题已经通过构建一个解决区间树 https en wikipedia org wiki Interval tree 或称为范围树或线段树 已经针对一
  • 绘制方程

    我正在尝试创建一个函数 它将绘制我告诉它的任何公式 import numpy as np import matplotlib pyplot as plt def graph formula x range x np array x rang
  • 添加不同形状的 numpy 数组

    我想添加两个不同形状的 numpy 数组 但不进行广播 而是将 缺失 值视为零 可能最简单的例子是 1 2 3 2 gt 3 2 3 or 1 2 3 2 1 gt 3 2 3 1 0 0 我事先不知道形状 我正在弄乱每个 np shape
  • Pandas:merge_asof() 对多行求和/不重复

    我正在处理两个数据集 每个数据集具有不同的关联日期 我想合并它们 但因为日期不完全匹配 我相信merge asof 是最好的方法 然而 有两件事发生merge asof 不理想的 数字重复 数字丢失 以下代码是一个示例 df a pd Da
  • 向 Altair 图表添加背景实心填充

    I like Altair a lot for making graphs in Python As a tribute I wanted to regenerate the Economist graph s in Mistakes we
  • 如何在seaborn displot中使用hist_kws

    我想在同一图中用不同的颜色绘制直方图和 kde 线 我想为直方图设置绿色 为 kde 线设置蓝色 我设法弄清楚使用 line kws 来更改 kde 线条颜色 但 hist kws 不适用于显示 我尝试过使用 histplot 但我无法为
  • 每个 X 具有多个 Y 值的 Python 散点图

    我正在尝试使用 Python 创建一个散点图 其中包含两个 X 类别 cat1 cat2 每个类别都有多个 Y 值 如果每个 X 值的 Y 值的数量相同 我可以使用以下代码使其工作 import numpy as np import mat
  • Python Selenium:如何在文本文件中打印网站上的值?

    我正在尝试编写一个脚本 该脚本将从 tulsaspca org 网站获取以下 6 个值并将其打印在 txt 文件中 最终输出应该是 905 4896 7105 23194 1004 42000 放置的动物 的 HTML span class

随机推荐