Python装饰器Decorators

2023-05-16

文章目录

  • 一、功能
  • 二、@语法糖
  • 三、*args、**kwargs
  • 四、带参数的装饰器
  • 五、类装饰器
  • 六、装饰器顺序

一、功能

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。

有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。

eg:先来看一个简单例子

def foo():
    print('i am foo')

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def foo():
    print('i am foo')
    logging.info("foo is running")

如果函数 bar()、bar2() 也有类似的需求,怎么做?再写一个 logging 在 bar 函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个新的函数:专门处理日志 ,日志处理完之后再执行真正的业务代码

def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()

def foo():
    print('i am foo')

use_logging(foo)

这样做逻辑上是没问题的,功能是实现了,但是我们调用的时候不再是调用真正的业务逻辑 foo 函数,而是换成了 use_logging 函数,这就破坏了原有的代码结构, 现在我们不得不每次都要把原来的那个 foo 函数作为参数传递给 use_logging 函数,那么有没有更好的方式的呢?当然有,答案就是装饰器。

简单装饰器

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapper

def foo():
    print('i am foo')

foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapper
foo()                   # 执行foo()就相当于执行 wrapper()

use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。

在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。

二、@语法糖

@ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作。

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()
    return wrapper

@use_logging
def foo():
    print("i am foo")

foo()

有了 @ ,我们就可以省去foo = use_logging(foo)这一句了,直接调用 foo() 即可得到想要的结果;

foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

三、*args、**kwargs

如果我的业务逻辑函数 foo 需要参数怎么办?比如:

def foo(name):
    print("i am %s" % name)

我们可以在定义 wrapper 函数的时候指定参数:

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
        return func(name)
return wrapper

这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。

当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
return wrapper

如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?
比如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

这时,你就可以把 wrapper 函数指定关键字函数:

def wrapper(*args, **kwargs):
        # args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
return wrapper

四、带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。

装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。 这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。

当我们使用@use_logging(level=“warn”)调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn") 等价于 @decorator

五、类装饰器

装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()
functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的__doc__、__name__、参数列表,先看例子:

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__)      # 输出 'f'
        print(func.__doc__)       # 输出 does some math
        print("装饰器")
        return func(*args, **kwargs)
    return with_logging
# 函数


@logged
def f(x):
    """does some math"""
    return x + x * x


f(2)

print(f.__name__)     # 输出 'with_logging'
print(f.__doc__)       # 输出 None

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。

好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这样装饰器的外部想要调用被装饰对象的属性时,加 wraps 才有意义


from functools import wraps


def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__)     # 输出 'f'

        print(func.__doc__)       # 输出 'does some math'
        print("装饰器")

        return func(*args, **kwargs)
    return with_logging


# 函数
@logged
def f(x):
    """does some math"""
    return x + x * x


f(2)

print(f.__name__)     # 输出 'f'
print(f.__doc__)       # 输出 'does some math'

六、装饰器顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)))

ref:Python 函数装饰器

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

Python装饰器Decorators 的相关文章

  • 尽管极其懒惰,但如何在 Python 中模拟 IMAP 服务器?

    我很好奇是否有一种简单的方法来模拟 IMAP 服务器 例如imaplib模块 在Python中 without做很多工作 是否有预先存在的解决方案 理想情况下 我可以连接到现有的 IMAP 服务器 进行转储 并让模拟服务器在真实的邮箱 电子
  • Django REST序列化器:创建对象而不保存

    我已经开始使用 Django REST 框架 我想做的是使用一些 JSON 发布请求 从中创建一个 Django 模型对象 然后使用该对象而不保存它 我的 Django 模型称为 SearchRequest 我所拥有的是 api view
  • 如何在刻度标签和轴之间添加空间

    我已成功增加刻度标签的字体 但现在它们距离轴太近了 我想在刻度标签和轴之间添加一点呼吸空间 如果您不想全局更改间距 通过编辑 rcParams 并且想要更简洁的方法 请尝试以下操作 ax tick params axis both whic
  • Python PAM 模块的安全问题?

    我有兴趣编写一个 PAM 模块 该模块将利用流行的 Unix 登录身份验证机制 我过去的大部分编程经验都是使用 Python 进行的 并且我正在交互的系统已经有一个 Python API 我用谷歌搜索发现pam python http pa
  • Pycharm Python 控制台不打印输出

    我有一个从 Pycharm python 控制台调用的函数 但没有显示输出 In 2 def problem1 6 for i in range 1 101 2 print i end In 3 problem1 6 In 4 另一方面 像
  • 导入错误:没有名为 _ssl 的模块

    带 Python 2 7 的 Ubuntu Maverick 我不知道如何解决以下导入错误 gt gt gt import ssl Traceback most recent call last File
  • Spark的distinct()函数是否仅对每个分区中的不同元组进行洗牌

    据我了解 distinct 哈希分区 RDD 来识别唯一键 但它是否针对仅移动每个分区的不同元组进行了优化 想象一个具有以下分区的 RDD 1 2 2 1 4 2 2 1 3 3 5 4 5 5 5 在此 RDD 上的不同键上 所有重复键
  • 如何使用装饰器禁用某些功能的中间件?

    我想模仿的行为csrf exempt see here https docs djangoproject com en 1 11 ref csrf django views decorators csrf csrf exempt and h
  • 在 NumPy 中获取 ndarray 的索引和值

    我有一个 ndarrayA任意维数N 我想创建一个数组B元组 数组或列表 其中第一个N每个元组中的元素是索引 最后一个元素是该索引的值A 例如 A array 1 2 3 4 5 6 Then B 0 0 1 0 1 2 0 2 3 1 0
  • Python 中的二进制缓冲区

    在Python中你可以使用StringIO https docs python org library struct html用于字符数据的类似文件的缓冲区 内存映射文件 https docs python org library mmap
  • feedparser 在脚本运行期间失败,但无法在交互式 python 控制台中重现

    当我运行 eclipse 或在 iPython 中运行脚本时 它失败了 ascii codec can t decode byte 0xe2 in position 32 ordinal not in range 128 我不知道为什么 但
  • 在pyyaml中表示具有相同基类的不同类的实例

    我有一些单元测试集 希望将每个测试运行的结果存储为 YAML 文件以供进一步分析 YAML 格式的转储数据在几个方面满足我的需求 但测试属于不同的套装 结果有不同的父类 这是我所拥有的示例 gt gt gt rz shorthand for
  • Abaqus 将曲面转化为集合

    我一直试图在模型中找到两个表面的中心 参见照片 但未能成功 它们是元素表面 面 查询中没有选项可以查找元素表面的中心 只能查找元素集的中心 找到节点集的中心也很好 但是我的节点集没有出现在工具 gt 查询 gt 质量属性选项中 而且我找不到
  • 如何将 numpy.matrix 提高到非整数幂?

    The 运算符为numpy matrix不支持非整数幂 gt gt gt m matrix 1 0 0 5 0 5 gt gt gt m 2 5 TypeError exponent must be an integer 我想要的是 oct
  • 从 pygame 获取 numpy 数组

    我想通过 python 访问我的网络摄像头 不幸的是 由于网络摄像头的原因 openCV 无法工作 Pygame camera 使用以下代码就像魅力一样 from pygame import camera display camera in
  • Nuitka 未使用 nuitka --recurse-all hello.py [错误] 编译 exe

    我正在尝试通过 nuitka 创建一个简单的 exe 这样我就可以在我的笔记本电脑上运行它 而无需安装 Python 我在 Windows 10 上并使用 Anaconda Python 3 我输入 nuitka recurse all h
  • 如何在 Django 中使用并发进程记录到单个文件而不使用独占锁

    给定一个在多个服务器上同时执行的 Django 应用程序 该应用程序如何记录到单个共享日志文件 在网络共享中 而不保持该文件以独占模式永久打开 当您想要利用日志流时 这种情况适用于 Windows Azure 网站上托管的 Django 应
  • 如何使用google colab在jupyter笔记本中显示GIF?

    我正在使用 google colab 想嵌入一个 gif 有谁知道如何做到这一点 我正在使用下面的代码 它并没有在笔记本中为 gif 制作动画 我希望笔记本是交互式的 这样人们就可以看到代码的动画效果 而无需运行它 我发现很多方法在 Goo
  • Python - 字典和列表相交

    给定以下数据结构 找出这两种数据结构共有的交集键的最有效方法是什么 dict1 2A 3A 4B list1 2A 4B Expected output 2A 4B 如果这也能产生更快的输出 我可以将列表 不是 dict1 组织到任何其他数
  • Python:元类属性有时会覆盖类属性?

    下面代码的结果让我感到困惑 class MyClass type property def a self return 1 class MyObject object metaclass MyClass a 2 print MyObject

随机推荐

  • Intel RealSense L515&Unreal Engine 4调试记录

    文章目录 前言一 安装与配置1 安装前置条件2 配置 二 编译与运行1 编译2 运行 填坑与测试1 填坑2 测试 前言 Intel RealSense系列推出了适用于Unreal Engine 4的相关插件 xff0c 官网提供了相关示例代
  • Intel RealSense L515 motion的计算与可视化

    文章目录 前言 一 环境准备 二 具体步骤 1 示例下载 2 代码编译 3 填坑 前言 前面的文章介绍了将L515数据映射至UE当中 本篇文章将针对Intel RealSense SDK 2 0 进行姿势的计算与可视化 一 环境准备 Int
  • PELCO-D协议 要点整理

    消息格式 Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6Byte 7Sync ByteAddressCommand 1Command 2Data 1Data 2Checksum The synchronizatio
  • GTEST/GMOCK介绍与实战:Gtest Sample9

    文章目录 1 简介2 用法 1 简介 示例 9显示了使用侦听器API修改谷歌Test的控制台输出和使用其反射API来检查测试结果 2 用法 span class token comment This sample shows how to
  • Gtest输出单元测试报告和输出覆盖率报告

    文章目录 1 要求2 生成gtest测试报告3 生成gtest覆盖率报告 1 要求 编译工具 xff1a 选择Cmake xff0c 单元测试使用Gtest 2 生成gtest测试报告 gtest本身仅能输出xml或者json格式的测试报告
  • GTEST/GMOCK介绍与实战:Gtest Sample10

    文章目录 1 简介2 用法 1 简介 示例 10展示了如何使用侦听器API来实现基本内存泄漏检查 2 用法 span class token comment This sample shows how to use Google Test
  • Bitbake与Yocto

    文章目录 一 Bitbake二 Yocto 一 Bitbake xff08 1 xff09 使用教程可以参考 xff1a BitBake 实用指南 xff0c 大部分步骤跟着操作即可了解bitbake的工作流程 xff1b 他主要参考和翻译
  • 随机漫步

    span class token keyword import span numpy span class token keyword as span np span class token keyword import span rand
  • UTC时间和PTP精确时间协议

    文章目录 一 GMT二 UTC三 GMT vs UTC四 C 43 43 获得当前的UTC时间 一 GMT GMT xff08 Greenwich Mean Time xff09 xff0c 格林威治平时 xff08 也称格林威治时间 xf
  • AutoSar系列之:AutoSar发展

    文章目录 一 Autosar成员二 Autosar历史发展三 使用Autosar前的状态1 原始状态2 进阶状态 四 使用Autosar后的状态1 软硬件隔离2 Autosar优势 一 Autosar成员 二 Autosar历史发展 三 使
  • AutoSar系列之:AutoSar概述

    文章目录 一 Autosar是什么二 架构 一 Autosar是什么 RTE xff1a 用与传递应用层软件和基础软件从之间的信号的 xff1b 隔离应用软件层和基础软件层 xff1b 其中一个层修改了 xff0c 不会影响另外一个层 xf
  • Autosar系列之Appl概述

    文章目录 一 Appl的组成1 SWC通信2 SWC分配 一 Appl的组成 SWC xff1a 应用软件组件 Autosar接口 xff1a SWC之间连接的端口 Runnable xff1a 可运行实体 xff0c SWC里面的一些函数
  • Autosar系列之SWC类型

    文章目录 一 原子级SWC二 集合级SWC三 特殊的SWC 一 原子级SWC 含义 xff1a 不可拆解的SWC 二 集合级SWC eg xff1a 将相似的功能放在一起 三 特殊的SWC IoHwAb xff0c Cdd 在原有的Auto
  • 汽车操作系统

    文章目录 一 汽车控制器类型二 Hypervisor三 QNX Linux Andorid四 Automotive Grade Linux 系统 xff08 AGL xff09 1 介绍2 IVI市场现状3 系统构建 xff08 1 xff
  • Autosar系列之Ports类型

    文章目录 一 接口二 接口类型三 S R接口四 C S 接口 一 接口 接口是连接2个SWC通信的 二 接口类型 三 S R接口 发送 接受数据传输接口 一般通过全局变量才传递 四 C S 接口 客户 服务接口 xff1b 通过函数Runn
  • Autosar系列之Runnable可运行实体

    文章目录 一 RUnnable Entity 一 RUnnable Entity 可运行实体 xff0c 其实就是 C文件内的函数而已 一个SWC可以包含多个Runnable Entity xff0c 就是一个 C文件中可以包含多个函数 x
  • Autosar系列之RTE

    文章目录 一 RTE二 RTE功能 一 RTE RTE Run TIme Environment 是Autosar体系结构的核心 RTE是Autosar软件架构中 xff0c 介于应用层和基础软件层之间 xff0c 是Autosar虚拟功能
  • Autosar系列之Autosar应用层整体入门

    文章目录 一 整个功能示意图二 软件组件SWC分类三 SWC组件 xff1a ports1 发送 接收端口Sender Receiver2 客户端 服务端端口Client Server 四 可运行实体Runnables五 BSW1 微控制器
  • ubuntu下mysql数据库的设置

    gt su root gt mysql span class token operator span u root span class token operator span p gt show databases span class
  • Python装饰器Decorators

    文章目录 一 功能二 64 语法糖三 args kwargs四 带参数的装饰器五 类装饰器六 装饰器顺序 一 功能 装饰器本质上是一个 Python 函数或类 xff0c 它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能 xf