Python装饰器

2023-05-16

Python的装饰器(decorator)可以说是Python的一个神器,它可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能。Python的装饰器同时也是Python学习从入门到精通过程中必需要熟练掌握的知识。小编我当初学习Python时差点被装饰器搞晕掉,今天尝试用浅显的语言解释下Python装饰器的工作原理及如何编写自己的装饰器吧。

Python装饰器的本质

Python的装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。这样我们可以在不改变被装饰函数的代码的情况下给被装饰函数或程序添加新的功能。Python的装饰器广泛应用于缓存、权限校验(如django中的@login_required和@permission_required装饰器)、性能测试(比如统计一段程序的运行时间)和插入日志等应用场景。有了装饰器,我们就可以抽离出大量与函数功能本身无关的代码,增加一个函数的重用性。

试想你写了很多程序,一直运行也没啥问题。有一天老板突然让你统计每个程序都运行了多长时间并比较下运行效率。此时如果你去手动修改每个程序的代码一定会让你抓狂,而且还破坏了那些程序的重用性。聪明的程序员是绝不能干这种蠢事的。此时你可以编写一个@time_it的装饰器(代码如下所示)。如果你想打印出某个函数或程序运行时间,只需在函数前面@一下,是不是很帅?

import time

def time_it(func):
    def inner():
        start = time.time()
        func()
        end = time.time()
        print('用时:{}秒'.format(end-start))
    return inner

@time_it
def func1():
    time.sleep(2)
    print("Func1 is running.")

if __name__ == '__main__':
    func1()

运行结果如下:

Func1 is running.

用时:2.0056326389312744

由于Python装饰器的工作原理主要依赖于嵌套函数和闭包,所以我们必须先对嵌套函数和闭包有深入的了解。嵌套函数和闭包几乎是Python工作面试必考题哦。

嵌套函数

如果在一个函数的内部还定义了另一个函数(注意: 是定义,不是引用!),这个函数就叫嵌套函数。外部的我们叫它外函数,内部的我们叫他内函数。

我们先来看一个最简单的嵌套函数的例子。我们在outer函数里又定义了一个inner函数,并调用了它。你注意到了吗? 内函数在自己作用域内查找局部变量失败后,会进一步向上一层作用域里查找。

def outer():
    x = 1
    def inner():
        y = x + 1
        print(y)
    inner()

outer() #输出结果 2
如果我们在外函数里不直接调用内函数,而是通过return inner返回一个内函数的引用 这时会发生什么呢? 你将会得到一个内函数对象,而不是运行结果。

def outer():
    x = 1
    def inner():
        y = x + 1
        print(y)
    return inner

outer() # 输出<function outer..inner at 0x039248E8>
f1 = outer()
f1() # 输出2
上述这个案例比较简单,因为outer和inner函数都是没有参数的。我们现在对上述代码做点改动,加入参数。你可以看到外函数的参数或变量可以很容易传递到内函数。

def outer(x):
    a = x

    def inner(y):
        b = y
        print(a+b)

    return inner

f1 = outer(1) # 返回inner函数对象
f1(10) # 相当于inner(10)。输出11
如果上例中外函数的变量x换成被装饰函数对象(func),内函数的变量y换成被装饰函数的参数,我们就可以得到一个通用的装饰器啦(如下所示)。你注意到了吗? 我们在没对func本身做任何修改的情况下,添加了其它功能, 从而实现了对函数的装饰。

def decorator(func):
    def inner(*args, **kwargs):
        add_other_actions()
        return func(*args, **kwargs)
    return inner

请你仔细再读读上面这段代码,我们的decorator返回的仅仅是inner函数吗? 答案是不。它返回的其实是个闭包(Closure)。整个装饰器的工作都依赖于Python的闭包原理。

闭包(Closure)

闭包是Python编程一个非常重要的概念。如果一个外函数中定义了一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。我们在看下之间案例。我们的outer方法返回的只是内函数对象吗? 错。我们的outer函数返回的实际上是一个由inner函数和外部引用变量(a)组成的闭包!

def outer(x):
    a = x

    def inner(y):
        b = y
        print(a+b)

    return inner

f1 = outer(1) # 返回inner函数对象+局部变量1(闭包)
f1(10) # 相当于inner(10)。输出11

一般一个函数运行结束的时候,临时变量会被销毁。但是闭包是一个特别的情况。当外函数发现,自己的临时变量会在将来的内函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量同内函数绑定在一起。这样即使外函数已经结束了,内函数仍然能够使用外函数的临时变量。这就是闭包的强大之处。

如何编写一个通用的装饰器

我们现在可以开始动手写个名为hint的装饰器了,其作用是在某个函数运行前给我们提示。这里外函数以hint命名,内函数以常用的wrapper(包裹函数)命名。

def hint(func):
    def wrapper(*args, **kwargs):
        print('{} is running'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapper

@hint
def hello():
    print("Hello!")

我们现在对hello已经进行了装饰,当我们调用hello()时,我们可以看到如下结果。

>>> hello()
hello is running.
Hello!

值得一提的是被装饰器装饰过的函数看上去名字没变,其实已经变了。当你运行hello()后,你会发现它的名字已经悄悄变成了wrapper,这显然不是我们想要的(如下图所示)。这一点也不奇怪,因为外函数返回的是由wrapper函数和其外部引用变量组成的闭包。

>>> hello.__name__
'wrapper'

为了解决这个问题保证装饰过的函数__name__属性不变,我们可以使用functools模块里的wraps方法,先对func变量进行wraps。下面这段代码可以作为编写一个通用装饰器的示范代码,注意收藏哦。

from functools import wraps

def hint(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('{} is running'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapper


@hint
def hello():
    print("Hello!")

恭喜你,你已经学会写一个比较通用的装饰器啦,并保证装饰过的函数__name__属性不变啦。当然使用嵌套函数也有缺点,比如不直观。这时你可以借助Python的decorator模块(需事先安装)可以简化装饰器的编写和使用。如下所示。

from decorator import decorator

@decorator
def hint(func, *args, **kwargs):
    print('{} is running'.format(func.__name__))
    return func(*args, **kwargs)
 

编写带参数的高级装饰器

前面几个装饰器一般是内外两层嵌套函数。如果我们需要编写的装饰器本身是带参数的,我们需要编写三层的嵌套函数,其中最外一层用来传递装饰器的参数。现在我们要对@hint装饰器做点改进,使其能通过@hint(coder=“John”)传递参数。该装饰器在函数运行前给出提示的时候还显示函数编写人员的名字。完整代码如下所示:

from functools import wraps


def hint(coder):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print('{} is running'.format(func.__name__))
            print('Coder: {}'.format(coder))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper


@hint(coder="John")
def hello():
    print("Hello!")

下面这段代码是一段经典的Python装饰器代码,显示了@cache这个装饰器怎么编写和工作的。它需要使用缓存实例做为一个参数,所以也是三层嵌套函数。

import time
from functools import wraps

装饰器增加缓存功能

def cache(instance):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            # 构建key: key => func_name::args::kwargs
            joint_args = ','.join((str(x) for x in args))
            joint_kwargs = ','.join('{}={}'.format(k, v) for k, v in sorted(kwargs.items()))
            key = '{}::{}::{}'.format(func.__name__,joint_args, joint_kwargs)
            # 根据key获取结果。如果key已存在直接返回结果,不用重复计算。
         result = instance.get(key)
            if result is not None:
                return result
            # 如果结果不存在,重新计算,缓存。
         result = func(*args, **kwargs)
            instance.set(key, result)
            return result
        return inner_wrapper
    return wrapper

创建字典构造函数,用户缓存K/V键值对

class DictCache:
    def __init__(self):
        self.cache = dict()

    def get(self, key):
        return self.cache.get(key)

    def set(self, key, value):
        self.cache[key] = value

    def __str__(self):
        return str(self.cache)

    def __repr__(self):
        return repr(self.cache)

创建缓存对象

cache_instance = DictCache()

Python语法糖调用装饰器

@cache(cache_instance)
def long_time_func(x):
    time.sleep(x)
    return x

调用装饰过函数

long_time_func(3)

基于类实现的装饰器
Python的装饰器不仅可以用嵌套函数来编写,还可以使用类来编写。其调用__init__方法创建实例,传递参数,并调用__call__方法实现对被装饰函数功能的添加。

from functools import wraps


#类的装饰器写法, 不带参数
class Hint(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('{} is running'.format(self.func.__name__))
        return self.func(*args, **kwargs)


#类的装饰器写法, 带参数
class Hint(object):
    def __init__(self, coder=None):
        self.coder = coder

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('{} is running'.format(func.__name__))
            print('Coder: {}'.format(self.coder))
            return func(*args, **kwargs)     # 正式调用主要处理函数
        return wrapper

原文链接:https://blog.csdn.net/weixin_42134789/article/details/84635252

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

Python装饰器 的相关文章

  • PyList_SetItem 与 PyList_SETITEM

    据我所知 PyList SetItem 和 PyList SETITEM 之间的区别在于 PyList SetItem 会降低它覆盖的列表项的引用计数 而 PyList SETITEM 不会 我有什么理由不应该一直使用 PyList Set
  • 编辑 scikit-learn 决策树

    我想编辑 sklearn DecisionTree 例如改变条件或切割节点 叶子等 但似乎没有功能可以做到这一点 如果我可以导出到文件 编辑它以导入 如何编辑决策树 环境 Windows 10 python3 3 sklearn 0 17
  • Python 遍历目录树的方法是什么?

    我觉得分配文件和文件夹并执行 item 部分有点黑客 有什么建议么 我正在使用Python 3 2 from os import from os path import def dir contents path contents list
  • 为什么在 Windows 中使用 GetConsoleScreenBufferInfoEx 时控制台窗口会缩小?

    我正在尝试使用 GetConsoleScreenBufferInfoEx 和 SetConsoleScreenBufferInfoEx 设置 Windows 命令行控制台的背景和前景色 我正在 Python 中使用 wintypes 进行此
  • 将 API 数据存储到 DataFrame 中

    我正在运行 Python 脚本来从 Interactive Brokers API 收集金融市场数据 连接到API后 终端打印出请求的历史数据 如何将数据保存到数据帧中而不是在终端中流式传输 from ibapi wrapper impor
  • html 解析器 python

    我正在尝试解析一个网站 我正在使用 HTMLParser 模块 问题是我想解析第一个 a href 评论后 但我真的不知道该怎么做 所以我在文档中发现有一个函数叫做handle comment 但我还没有找到如何正确使用它 我有以下内容 i
  • 如何使用循环将十进制转换为二进制?

    我想编写一个程序 将十进制数 0 到 9 转换为二进制数 我可以编写如何使用重复除法将十进制数转换为二进制数的代码 但是 我在创建一个以二进制格式打印十进制数字 0 到 9 的循环时遇到了麻烦 这是我的代码 number 0 remaind
  • 为 Networkx 图添加标题?

    我希望我的代码创建一个带有标题的图 使用下面的代码 可以创建绘图 但没有标题 有人可以告诉我我做错了什么吗 import pandas as pd import networkx as nx from networkx algorithms
  • 如何使用 Python 多处理避免在分叉进程中加载​​父模块

    当您创建一个Pool使用Python的进程multiprocessing 这些进程将分叉 父进程中的全局变量将显示在子进程中 如下面的问题所述 如何限制多处理进程的范围 https stackoverflow com questions 2
  • 如何从 Python 中指定运行程序的输入文件?

    我正在编写一个外部脚本 以通过笔记本电脑上的 Python mrjob 模块 而不是在 Amazon Elastic Compute Cloud 或任何大型集群上 运行 mapreduce 作业 我读自mrjob文档 http packag
  • 杂乱的扭曲连接在不干净的时尚中消失了。没有代理。已经尝试过标题

    我正在尝试抓取这个网站 https www5 apply2jobs com jupitermed ProfExt index cfm fuseaction mExternal searchJobs https www5 apply2jobs
  • RuntimeError: 预期所有张量都在同一设备上,但发​​现至少有两个设备,cpu 和 cuda:0!使用我的模型进行预测时

    我使用变压器训练了一个序列分类模型 BertForSequenceClassification 我收到错误 预计所有张量都在同一设备上 但发 现至少有两个设备 cpu 和 cuda 0 在方法wrapper index select中检查参
  • 一起使用 Flask 和 Tornado?

    我是以下的忠实粉丝Flask 部分是因为它很简单 部分是因为它有很多扩展 http flask pocoo org extensions 然而 Flask 是为了在 WSGI 环境中使用而设计的 而 WSGI 不是非阻塞的 所以 我相信 它
  • numpy.cov() 返回意外的输出

    我有一个 X 数据集 有 9 个特征和 683 行 683x9 我想获取这个 X 数据集和另一个与 X 具有相同形状的数据集的协方差矩阵 我使用np cov originalData generatedData rowvar False 代
  • 如何在C++中列出Python模块的所有函数名称?

    我有一个 C 程序 我想导入一个 Python 模块并列出该模块中的所有函数名称 我该怎么做 我使用以下代码从模块中获取字典 PyDictObject pDict PyDictObject PyModule GetDict pModule
  • 如何强制 Y 轴仅使用整数

    我正在使用 matplotlib pyplot 模块绘制直方图 我想知道如何强制 y 轴标签仅显示整数 例如 0 1 2 3 等 而不显示小数 例如 0 0 5 1 1 5 2 等 我正在查看指导说明并怀疑答案就在附近matplotlib
  • 如何向 SCons 构建添加预处理和后处理操作?

    我正在尝试在使用 SCons 构建项目时添加预处理和后处理操作 SConstruct 和 SConscript 文件位于项目的顶部 预处理动作 生成代码 通过调用不同的工具 gt 不知道在此预处理之后将生成的确切文件 可以创建用于决定生成哪
  • 为什么 bot.get_channel() 会产生 NoneType?

    我正在制作一个 Discord 机器人来处理公告命令 当使用该命令时 我希望机器人在特定通道中发送一条消息 并向用户发送一条消息以表明该命令已发送 但是 我无法将消息发送到频道 我尝试了这段代码 import discord import
  • 最小硬币找零问题——回溯

    我正在尝试用最少数量的硬币解决硬币找零问题 采用回溯法 我实际上已经完成了它 但我想添加一些选项 按其单位打印硬币数量 而不仅仅是总数 这是我下面的Python代码 def minimum coins coin list change mi
  • 在游戏中实现功能

    我在完成这部分作业时遇到了麻烦 我必须宣布游戏的获胜者 然后输入到函数中 输入所有 if 语句后 我必须创建一个函数def playGame 这必须包括 showRules user getUserChoice computer getCo

随机推荐

  • STL中vector的使用

    STL中vector vector的使用 include lt iostream gt include lt vector gt using namespace std 使用命名空间 int main vector lt int gt v1
  • Fibonacci的四种求解方法

    Fibonacci Sequence的使用 include lt iostream gt using namespace std 使用命名空间 T N 61 O N 2 61 T N 1 43 T N 2 43 2 int fib2 int
  • 最大子序列和问题

    最大子序列和问题Maximum Subsequence Sum include lt iostream gt include lt vector gt using namespace std 使用命名空间 T N 61 O N 3 Cubi
  • C++基础之基础

    C 43 43 xff08 容纳了好几种编程范式 xff09 xff1a 面向对象编程 泛型编程 过程化编程 面向对象编程 xff1a 其本质是以建立模型体现出来的抽象思维过程和面向对象的方法 抽象 继承 多态 xff1a 抽象性是指将具有
  • 这五大MySQL在线课程,最适合初学者的你!

    全文共3214字 xff0c 预计学习时长7分钟 图片来源 xff1a pexels com 64 pixabay 过去几年 xff0c 有句话越来越流行 xff1a 人人必须学习如何编码 这值得鼓励 在当今以信息和技术为中心的世界里 xf
  • 课程作业(单链表C++实现)

    单链表的操作C 43 43 实现 include lt iostream gt using namespace std 使用命名空间 template lt typename dataType gt 定义一个数据类型模板 class lin
  • 课程作业(二叉查找树)

    mainTest cpp include lt iostream gt include 34 binarySearchTree h 34 using namespace std int main cout lt lt 34 请按前序输入一棵
  • C++程序设计实践指导——第一章 简单编程 (2)

    第一章 简单编程 xff08 2 xff09 1 9 统计与替换字符串中的关键字 建立一个类WordNum xff0c 统计一个英文字符串中的英文单词个数 字符串中的各英文单i司以一个或多个空格分隔 如字符串 34 I am a stude
  • STL+Python+图像处理-学习资源

    1 C 43 43 STL学习网站 CPlusPlus com CppReference com gcc gnu org 2 Python学习书籍及网站 Python Crash Course Learn Python the Hard W
  • STL----------C++Primer(笔记)

    1 string string word cin gt gt word getline cin word 关系操作符 lt lt 61 gt gt 61 include lt cctype gt 头文件 string s 61 34 Hel
  • 侯捷-STL与泛型编程(GP)笔记

    1 stl体系结构基础介绍 分配器 xff08 allocator xff09 xff1a 主管分配内存 适配器 xff08 adaptor xff09 xff1a 进行一个转换 xff0c 与另一个对象绑定 include lt iost
  • 课程作业——数据结构与算法C++(1)

    课程作业6 xff08 归并排序 冒泡排序 插入排序 选择排序 xff09 归并排序 冒泡排序 插入排序 选择排序 主程序 include lt iostream gt include lt vector gt using std vect
  • C++程序设计实践指导——第二章 样例讲解

    复数计算器 include lt iostream gt include lt cmath gt include lt string gt include lt fstream gt include lt ctime gt using na
  • 步进电机控制与LCD显示L297与L298

    步进电机控制与LCD显示L297与L298 上次介绍了PWM和L298结合的电机调速 xff01 接下来介绍L297与L298结合的例子 xff01 PWM电机调速 下面是L297的简介 xff1a L297是步进电机专用控制器 xff0c
  • 变分自编码器(一):原来是这么一回事

    https kexue fm archives 5253 过去虽然没有细看 xff0c 但印象里一直觉得变分自编码器 xff08 Variational Auto Encoder xff0c VAE xff09 是个好东西 于是趁着最近看概
  • Linux 安装npm

    1 root 登录linux 2 cd usr loacl node 没有目录就自己创建一个 3 wget https npm taobao org mirrors node v4 4 7 node v4 4 7 linux x64 tar
  • DE1-SOC开发笔记

    verilog FPGA 采用verilog开发语言 xff0c 使用时序和组合逻辑 进行行为 xff0c 数据流 xff0c 结构建模 RTL级编程 xff0c 在实际板卡上面验证逻辑的正确性 sopc xff1a 软硬件结合的开发方式
  • 关于立创EDA使用的几点心得

    对于立创EDA 与AD仅为小白 xff0c 仅布过简单的双层板 xff0c 以下仅记录自己的几点心得 1 如果想要在立创商城 xff0c 嘉立创实现打板贴片一体化 xff0c 采用的普遍的两种方法 xff1a 立创EDA xff0c 有在线
  • 《当下即是生活》季羡林——读书笔记

    目录 书籍简介 经典摘录 三思而行 满招损 xff0c 谦受益 牵就与适应 睁一只眼 闭一只眼 论压力 论恐惧 难得糊涂 春色满寰中 槐花 书籍简介 作者季羡林 本书精选季羡林关于人生活法的散文 xff0c 阐述一个人怎样活在当下 xff0
  • Python装饰器

    Python的装饰器 decorator 可以说是Python的一个神器 xff0c 它可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能 Python的装饰器同时也是Python学习从入门到精通过程中必需要熟练掌握的知识 小编我