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:查找围绕某个 GPS 位置的圆的 GPS 坐标的优雅方法

    我有一组以十进制表示的 GPS 坐标 并且我正在寻找一种方法来查找每个位置周围半径可变的圆中的坐标 这是一个例子 http green and energy com downloads test circle html我需要什么 这是一个圆
  • 元组有什么用?

    我现在正在学习 Python 课程 我们刚刚介绍了元组作为数据类型之一 我阅读了它的维基百科页面 但是 我无法弄清楚这种数据类型在实践中会有什么用处 我可以提供一些需要一组不可变数字的示例吗 也许是在 Python 中 这与列表有何不同 每
  • 安装了 32 位的 Python,显示为 64 位

    我需要运行 32 位版本的 Python 我认为这就是我在我的机器上运行的 因为这是我下载的安装程序 当我重新运行安装程序时 它会将当前安装的 Python 版本称为 Python 3 5 32 位 然而当我跑步时platform arch
  • Python 中的舍入浮点问题

    我遇到了 np round np around 的问题 它没有正确舍入 我无法包含代码 因为当我手动设置值 而不是使用我的数据 时 返回有效 但这是输出 In 177 a Out 177 0 0099999998 In 178 np rou
  • 处理 Python 行为测试框架中的异常

    我一直在考虑从鼻子转向行为测试 摩卡 柴等已经宠坏了我 到目前为止一切都很好 但除了以下之外 我似乎无法找出任何测试异常的方法 then It throws a KeyError exception def step impl contex
  • 用枢轴点拟合曲线 Python

    我有下面的图 我想用 2 条线来拟合它 使用 python 我设法适应上半部分 def func x a b x np array x return a x b popt pcov curve fit func up x up y 我想用另
  • 使用 Python 从文本中删除非英语单词

    我正在 python 上进行数据清理练习 我正在清理的文本包含我想删除的意大利语单词 我一直在网上搜索是否可以使用像 nltk 这样的工具包在 Python 上执行此操作 例如给出一些文本 Io andiamo to the beach w
  • YOLOv8获取预测边界框

    我想将 OpenCV 与 YOLOv8 集成ultralytics 所以我想从模型预测中获取边界框坐标 我该怎么做呢 from ultralytics import YOLO import cv2 model YOLO yolov8n pt
  • 在Python中连接反斜杠

    我是 python 新手 所以如果这听起来很简单 请原谅我 我想加入一些变量来生成一条路径 像这样 AAAABBBBCCCC 2 2014 04 2014 04 01 csv Id TypeOfMachine year month year
  • 为什么 PyYAML 花费这么多时间来解析 YAML 文件?

    我正在解析一个大约 6500 行的 YAML 文件 格式如下 foo1 bar1 blah name john age 123 metadata whatever1 whatever whatever2 whatever stuff thi
  • 如何使用 Mysql Python 连接器检索二进制数据?

    如果我在 MySQL 中创建一个包含二进制数据的简单表 CREATE TABLE foo bar binary 4 INSERT INTO foo bar VALUES UNHEX de12 然后尝试使用 MySQL Connector P
  • 如何通过 TLS 1.2 运行 django runserver

    我正在本地 Mac OS X 机器上测试 Stripe 订单 我正在实现这段代码 stripe api key settings STRIPE SECRET order stripe Order create currency usd em
  • Cython 和类的构造函数

    我对 Cython 使用默认构造函数有疑问 我的 C 类 Node 如下 Node h class Node public Node std cerr lt lt calling no arg constructor lt lt std e
  • Jupyter Notebook 找不到 Python 模块

    不知道发生了什么 但每当我使用 ipython 氢 原子 或 jupyter 笔记本时都找不到任何已安装的模块 我知道我安装了 pandas 但笔记本说找不到 我应该补充一点 当我正常运行脚本时 python script py 它确实导入
  • 如何使用原始 SQL 查询实现搜索功能

    我正在创建一个由 CS50 的网络系列指导的应用程序 这要求我仅使用原始 SQL 查询而不是 ORM 我正在尝试创建一个搜索功能 用户可以在其中查找存储在数据库中的书籍列表 我希望他们能够查询 书籍 表中的 ISBN 标题 作者列 目前 它
  • 如何在 Windows 命令行中使用参数运行 Python 脚本

    这是我的蟒蛇hello py script def hello a b print hello and that s your sum sum a b print sum import sys if name main hello sys
  • 为什么 Pickle 协议 4 中的 Pickle 文件是协议 3 中的两倍,而速度却没有任何提升?

    我正在测试 Python 3 4 我注意到 pickle 模块有一个新协议 因此 我对 2 个协议进行了基准测试 def test1 pickle3 open pickle3 wb for i in range 1000000 pickle
  • 如何解决 PDFBox 没有 unicode 映射错误?

    我有一个现有的 PDF 文件 我想使用 python 脚本将其转换为 Excel 文件 目前正在使用PDFBox 但是存在多个类似以下错误 org apache pdfbox pdmodel font PDType0Font toUnico
  • Django-tables2 列总计

    我正在尝试使用此总结列中的所有值文档 https github com bradleyayers django tables2 blob master docs pages column headers and footers rst 但页
  • Kivy - 单击按钮时编辑标签

    我希望 Button1 在单击时编辑标签 etykietka 但我不知道如何操作 你有什么想法吗 class Zastepstwa App def build self lista WebOps getList layout BoxLayo

随机推荐

  • 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