【Python】装饰器相关知识点

1. 闭包基础

知识点:

  • 闭包:由内外函数构成。外函数返回内函数,而内函数引用了外函数的变量,从而实现“闭包”效果,保证局部变量在函数调用结束后依然被保存。

代码示例:

def outer():x = 500  # 外函数变量def inner():y = 200return x + y  # 内函数引用了外部变量 xreturn inner  # 返回内函数本身,而非调用# 使用闭包
func = outer()
print("闭包返回结果:", func())

函数实现装饰器

2. 装饰器基础

知识点:

  • 装饰器:本质上是一种闭包,用于在不修改原函数代码的前提下,动态地为函数添加额外功能。下面示例中定义了一个 costTime 装饰器,用于统计函数执行时间。

代码示例:

import timedef costTime(func):print("执行了costTime函数")  # 装饰器在装饰时会执行一次def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)  # 调用原函数end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@costTime  # 等价于 add = costTime(add)
def add(a, b):time.sleep(1)  # 模拟耗时操作return a + bprint("add(1, 2) =", add(1, 2))

注意: 使用装饰器后,原函数 add 实际上变成了 inner 函数,元数据(如函数名)会发生变化。


3. 保留原函数元数据

知识点:

  • 为了避免装饰器覆盖原函数的元数据(例如 __name____doc__),可以使用 functools.wraps 装饰内层函数,使得原函数的元数据得以保留。

代码示例:

import time
from functools import wrapsdef costTime(func):print("执行了costTime函数")@wraps(func)def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@costTime
def add(a, b):"""求两个数的和"""time.sleep(1)return a + bprint("add(1, 2) =", add(1, 2))
print("函数名称:", add.__name__)
print("函数文档:", add.__doc__)

4. 带参数的装饰器

知识点:

  • 带参数的装饰器:需要使用三层函数来实现。最外层函数接收装饰器参数,中间层函数接收原函数,最内层函数完成对原函数的调用以及附加功能。

代码示例:

import timedef deco(name="root"):# 最外层函数,接收装饰器参数def costTime(func):# 中间层,接收原函数def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")print(f"传递的参数为:{name}")return resultreturn innerreturn costTime@deco(name="admin")
def add(a, b):time.sleep(1)return a + bprint("add(1, 2) =", add(1, 2))

1. 闭包与装饰器基本原理

  • 闭包概念

    • 闭包由外部函数和内部函数构成。内部函数可以访问外部函数的局部变量,即使外部函数已执行完毕。
  • 装饰器本质

    • 装饰器利用闭包特性,在不修改原函数代码的情况下,为其添加额外功能,如日志记录、性能计时、权限验证等。
    • 装饰器在定义时会先执行外层逻辑(例如打印提示),而返回的内部包装函数在调用时添加附加操作,再调用原函数。

2. 装饰器基础写法

  • 装饰器函数通常接收一个函数作为参数,并返回一个新的包装函数。
  • 包装函数内通常先执行预处理逻辑(如记录开始时间),再调用原函数,最后执行后续操作(如计算耗时、打印结果)。
  • 这种模式实现了对原函数功能的无侵入式扩展。

3. 保留原函数元数据

  • 使用装饰器后,包装函数会覆盖原函数的名称、文档字符串等元数据。
  • 为了保留这些信息,可以使用 functools.wraps 装饰包装函数,使得包装函数复制原函数的元数据。

4. 带参数的装饰器

  • 带参数的装饰器需要通过三层函数嵌套实现:
    • 最外层函数:接收装饰器参数,用于动态配置装饰行为。
    • 中间层函数:接收原函数,并返回包装函数。
    • 最内层函数:在调用原函数前后添加额外逻辑,同时传递所有参数。
  • 这种结构使装饰器可以灵活地根据传入参数调整附加功能。

5. 总结

  • 装饰器是 Python 中一种强大的功能扩展机制,基于高阶函数和闭包实现。
  • 常见应用场景包括性能统计、日志记录、缓存、权限控制等。
  • 设计装饰器时,注意保留原函数元数据(使用 functools.wraps)和正确处理参数传递问题。
  • 带参数的装饰器结构虽然稍复杂,但能够大幅提升装饰器的灵活性和可配置性。
    以下是关于使用类方法实现装饰器以及魔术方法__call__的学习笔记:

类实现装饰器

1. 使用类实现装饰器

在Python中,装饰器通常用于在不修改原函数代码的情况下,给函数增加额外的功能。除了使用函数实现装饰器外,还可以通过类来实现。这需要在类中实现__call__方法,使其实例化对象变为可调用对象,类似于函数。

示例:

import timeclass DecoClass:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):start = time.time()result = self.func(*args, **kwargs)end = time.time()print(f"执行{self.func.__name__}花了{end - start}s")return result@DecoClass  # 等同于:add = DecoClass(add)
def add(a, b):time.sleep(1)return a + bprint(add(1, 2))

说明:

  • DecoClass类的__init__方法接收一个函数作为参数,并将其赋值给实例变量self.func
  • __call__方法使得类的实例对象可以像函数一样被调用。在调用时,记录函数执行前后的时间,并计算执行时间。
  • 使用@DecoClass语法糖,将add函数装饰,使其在调用时自动计算并输出执行时间。

2. 带参数的装饰器类实现

有时,我们希望装饰器本身也能接受参数。这可以通过在类的__init__方法中接收装饰器参数,并在__call__方法中再定义并返回一个内部函数来实现。

示例:

import timeclass DecoClass:def __init__(self, name):self.name = namedef __call__(self, func):def inner(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"执行{func.__name__}花了{end - start}s")return resultreturn inner@DecoClass(name="admin")  # 等同于:add = DecoClass(name="admin")(add)
def add(a, b):time.sleep(1)return a + bprint(add(1, 2))

说明:

  • DecoClass类的__init__方法接收装饰器参数name,并将其赋值给实例变量self.name
  • __call__方法接收一个函数func作为参数,并定义一个内部函数inner,用于包装原函数的执行,并计算其执行时间。
  • 使用@DecoClass(name="admin")语法糖,将add函数装饰,使其在调用时自动计算并输出执行时间。

3. 魔术方法__call__

__call__是Python中的一种魔术方法,使得类的实例对象可以像函数一样被调用。通过在类中实现__call__方法,可以定义实例对象被调用时的行为。这在实现装饰器、回调函数等场景中非常有用。

示例:

class MyCallable:def __init__(self, value):self.value = valuedef __call__(self, increment):self.value += incrementprint(f"Value is now {self.value}")obj = MyCallable(10)
obj(5)  # 输出:Value is now 15

说明:

  • MyCallable类实现了__call__方法,因此其实例对象obj可以像函数一样被调用。
  • 调用obj(5)时,实际执行的是obj.__call__(5),这使得self.value增加了increment的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/38688.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ABC392题解

A 算法标签: 模拟 #include <iostream> #include <algorithm> #include <cstring>using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int w[3];for (int i 0; i < 3; i) cin >> w[i];sort(w, w 3);if (w[0]…

Quartus + VScode 实现模块化流水灯

文章目录 一、通过VScode编写Verilog代码二、模块化编程三、代码示例 一、通过VScode编写Verilog代码 1、下载Vscode 2、下载相关插件 搜索Verilog就会弹出有如图所示的插件&#xff0c;下载并安装 3、创建Quartus项目 4、创建完成后点击Tools&#xff0c;选择Options 然后在…

简单讲一下控制系统所用的PID公式

2025年3月23日电子电工实验室讲课大纲 哈喽&#xff0c;小伙伴们大家好&#xff0c;今天我们来讲一下PID&#xff01;首先我们的针对的场景是什么——循迹小车&#xff01; 就是我们刚入手STM32时&#xff0c;我们可能会制作一个循迹小车。我们想让那个小车走直线&#xff0c;但…

直观理解ECC椭圆曲线加密算法

数学还是挺有逻辑的&#xff0c;给出计算的操作步骤 就能得出想要结果 背景&#xff1a; ● ECC 是一种极其巧妙的 非对称加密算法 , 其完美利用了 椭圆曲线几何累加 不可逆的性质 ● 拥有 密钥体积小&#xff0c;计算速度快的优势&#xff0c;被广泛用于各种区块链&#xff0c…

深度解析 Android Matrix 变换(二):组合变换 pre、post

前言 在上一篇文章中&#xff0c;我们讲解了 Canvas 中单个变换的原理和效果&#xff0c;即缩放、旋转和平移。但是单个旋转仅仅是基础&#xff0c;Canvas 变换最重要的是能够随意组合各种变换以实现想要的效果。在这种情况下&#xff0c;就需要了解如何组合变换&#xff0c;以…

c++之迭代器

一.迭代器的基本概念 1.什么是迭代器 迭代器是一种对象&#xff0c;它提供了一种访问容器中各个元素的方法&#xff0c;同时隐藏了容器内部的实现细节。简单来说&#xff0c;迭代器就像是一个指针&#xff0c;它可以指向容器中的某个元素&#xff0c;并且能够通过一些操作&am…

在 .NET 9.0 Web API 中实现 Scalar 接口文档及JWT集成

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/90408075 介绍 随着 .NET 9 的发布&#xff0c;微软宣布他们将不再为任何 .NET API 项目提供默认的 Swagger gen UI。以前&#xff0c;当我们创建 .NET API 项目时&#xff0c;微软会自动添加 Swagger…

【操作系统笔记】操作系统的功能

上节课,我们学习了《什么是操作系统》。接下来,我们来看看操作系统有哪些功能? 这里讲的内容有两部分,一个是操作系统的目标,另外一个就是操作系统的功能。这两个细节可能会在考试的时候考到,但是最近好些年很少考到了。为了理解,我们还是一起来看一下。 操作系统的目标…

C/C++蓝桥杯算法真题打卡(Day7)

一、P8723 [蓝桥杯 2020 省 AB3] 乘法表 - 洛谷 算法代码&#xff1a; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;通常用于竞赛编程中简化代码 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用标准库函数时都要加std:: ty…

数据结构5(初):排序

目录 1、排序的概念以及常见的排序算法 1.1、排序的概念 1.2、常见的排序算法 2、常见排序算法的实现 2.1、插入排序 2.1.1、直接插入排序 2.1.2、希尔排序 2.2、选择排序 2.2.1、直接选择排序 2.2.2、堆排序 2.3、交换排序 2.3.1、冒泡排序 2.3.2、快速排序 2.3.…

VS2022中通过VCPKG安装的ceres之后调试ceres的例程设置

1.采用C20. vcpkg中设置: 2.增加预处理宏: GLOG_USE_GLOG_EXPORT 3.屏蔽sdl错误 在 项目-属性-C/C -命令行中添加 /sdl /w34996 #include "ceres/ceres.h" //#include <iostream> //#include<glog/logging.h>using ceres::AutoDiffCostFunction; usi…

Pydantic字段级校验:解锁@validator的12种应用

title: Pydantic字段级校验:解锁@validator的12种应用 date: 2025/3/23 updated: 2025/3/23 author: cmdragon excerpt: Pydantic校验系统支持通过pre验证器实现原始数据预处理,在类型转换前完成字符清洗等操作。格式验证涵盖正则表达式匹配与枚举值约束,确保护照编号等字…

函数递归和迭代

1.什么是递归&#xff1f; 在C语言中递归就是自己调用自己。 看一下简单函数的递归&#xff1a; 上面的代码实现演示一下函数的递归&#xff0c;最终是会陷入死循环的&#xff0c;栈溢出 。 1.1递归的思想&#xff1a; 把一个大型的问题一步一步的转换成一个个小的子问题来解…

发票查验/发票验真如何用Java实现接口调用

一、什么是发票查验&#xff1f;发票验真接口&#xff1f; 输入发票基本信息发票代码、发票号码、开票日期、校验码后6位、不含税金额、含税金额&#xff0c;核验发票真伪。 该接口也适用于机动车、二手车销售发票、航空运输电子客票、铁路电子客票等。 二、如何用Java实现接口…

AM32-MultiRotor-ESC项目固件编译和烧录方法介绍

AM32-MultiRotor-ESC项目固件编译和烧录方法介绍 &#x1f4cd;AM32-MultiRotor-ESC项目地址:https://github.com/AlkaMotors/AM32-MultiRotor-ESC-firmware&#x1f388;Updater with V8 Bootloader&#xff1a; https://github.com/AlkaMotors/F051_Bootloader_Updater&#…

HarmonyOS:@AnimatableExtend 装饰器自学指南

在最近的项目开发中&#xff0c;我遇到了需要实现复杂动画效果的需求。在探索解决方案的过程中&#xff0c;我发现了 AnimatableExtend 装饰器&#xff0c;它为实现动画效果提供了一种非常灵活且强大的方式。然而&#xff0c;在学习这个装饰器的过程中&#xff0c;我发现相关的…

Windows server 2022域控制服务器的配置

Windows server 2022介绍 一、核心特性与改进 安全核心服务器&#xff08;Secured-Core Server&#xff09; 硬件级安全&#xff1a;支持基于硬件的安全功能&#xff08;如TPM 2.0、Secure Boot、基于虚拟化的安全防护VBS&#xff09;&#xff0c;防止固件攻击。受信任的启动链…

C++语法之模板函数和模板类

模板函数是什么&#xff1f;就是不指定类型的函数&#xff0c;不指定类型如何写代码?所以得用到模板&#xff0c;可以先用模板代替&#xff0c;就好像方程式&#xff0c;先用x,y代替一样。 它的写法是这样&#xff0c;定义函数时&#xff0c;开头加一句:(其中的T就相当于x,y之…

时序分析笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、周期约束 二、建立时间和保持时间 三、时序路径 四、时序模型 前言 约束文件笔记&#xff0c;傅里叶的猫的视频。 一、周期约束 时序约束就是告诉软件输…

六十天前端强化训练之第二十八天之Composition 函数完全指南

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、核心概念解析 1.1 什么是 Composition 函数 1.2 为什么需要封装 1.3 设计原则 二、实战案例&#xff1a;鼠标跟踪器 2.1 未封装版本 2.2 封装后的 Composition 函数…