码农高天拆解Python装饰器 统一语法糖等价公式消解类嵌套分歧

【python】一个公式解决所有复杂的装饰器,理解了它以后任何装饰器都易如反掌! — 码农高天

码农高天

任何复杂装饰器结构均遵循被装饰对象等于装饰器调用的赋值逻辑,明确每层调用的输入输出即可彻底终结死记硬背。

AI-assisted summary of 【python】一个公式解决所有复杂的装饰器,理解了它以后任何装饰器都易如反掌! — original source. Check the original for full context.

Source published:

Key ideas

  • “类装饰器”(class decorator)存在歧义,它实际上涵盖了两个正交维度:“可以作为装饰器的类”与“装饰类的装饰器”,两者与普通函数可以自由交叉组合。 (0:00)
  • 理解复杂装饰器的核心在于还原语法糖的等价形式“被装饰对象 = 装饰器(被装饰对象)”,通过明确各层调用的输入与输出类型来推导代码结构,避免死记硬背。 (1:00)
  • 用类实现带参数装饰器时,类的 __init__ 负责接收配置参数,而 __call__ 方法则作为工厂接收被装饰函数并返回闭包包装函数 wrapper。 (5:00)
  • 在 Python 中类本身也是对象,装饰类的装饰器只需接收目标类对象,动态为其绑定或重写魔术方法(如 __str__)后再返回该类即可生效。 (7:30)

Detailed notes

## Background 该视频由技术博主“码农高天”于2023年10月16日发布在YouTube平台上。视频简介指出,本期内容在上一期装饰器讲解的基础上进一步拓展,引入了类作为装饰器以及装饰类的装饰器等多种复杂组合,旨在通过一个通用公式帮助观众轻松解决所有装饰器相关的疑问。

## TL;DR 高天首先澄清了“类装饰器”术语在语境上的歧义,指出装饰器本身的形式与被装饰对象是正交关系,并提炼出核心万能公式“被装饰对象 = 装饰器(被装饰对象)”。随后,他演示了如何利用类的 `__init__` 与 `__call__` 魔术方法实现普通计时器,并通过等价形式推导带参数的类装饰器内部结构改造。最后,视频讲解了如何编写直接装饰类的装饰器(如动态重载 `__str__`),并结合其开源项目 `object-print` 证明了该模式在实际开发与调试中的巨大实用价值。

## Atmosphere & style 视频整体呈现出清晰明快、干货满满且极具逻辑美感的技术拆解氛围。高天以轻松平实的口吻与观众对话,不依赖死记硬背的代码模板,而是手把手带着大家在代码编辑器中从底层原理推导,层层剥离语法糖的迷雾。整个讲解节奏沉稳自信、直击痛点,让原本令人头疼的复杂嵌套概念在公式还原下瞬间变得通透易懂。

## Bulletin - [00:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=0s) “类装饰器”(class decorator)存在歧义,它实际上涵盖了两个正交维度:“可以作为装饰器的类”与“装饰类的装饰器”,两者与普通函数可以自由交叉组合。 - [01:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=60s) 理解复杂装饰器的核心在于还原语法糖的等价形式“被装饰对象 = 装饰器(被装饰对象)”,通过明确各层调用的输入与输出类型来推导代码结构,避免死记硬背。 - [05:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=300s) 用类实现带参数装饰器时,类的 __init__ 负责接收配置参数,而 __call__ 方法则作为工厂接收被装饰函数并返回闭包包装函数 wrapper。 - [07:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=450s) 在 Python 中类本身也是对象,装饰类的装饰器只需接收目标类对象,动态为其绑定或重写魔术方法(如 __str__)后再返回该类即可生效。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=0s) 初学者常将装饰器简单定义为“输入是函数、输出也是函数的函数”,但这并不完全准确,无法覆盖更复杂的类相关场景。 - [00:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=30s) “类装饰器”术语有双重歧义:既可以指作为装饰器本身存在的类,也可以指修饰目标为类的装饰器,两者在概念上正交。 - [01:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=60s) 所有装饰器语法糖都遵循统一的等价赋值公式:被装饰对象 = 装饰器(被装饰对象)。 - [01:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=90s) 通过在类中实现 __call__ 魔术方法,其实例对象会变为 callable,能够像普通函数一样接收圆括号调用。 - [02:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=120s) 不带参数的类作为装饰器时,其 __init__ 接收被装饰函数,__call__ 则负责执行包装逻辑并调用原函数。 - [05:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=300s) 当类作为装饰器需要接收参数时(例如 @Timer(prefix='...')),其等价形式为 add = Timer(prefix='...')(add)。 - [05:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=330s) 在带参类装饰器中,__init__ 负责保存配置参数,__call__ 则转为接收原函数并返回内部 wrapper 闭包。 - [07:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=420s) 中文语境下将“可以作为装饰器的类”表述为“装饰器类”,在概念上比直接使用“类装饰器”更为精确清晰。 - [07:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=450s) 由于 Python 中的类本质也是对象,因此装饰类的装饰器其本质就是输入一个 class 对象、输出修改后 class 对象的函数。 - [08:30](https://www.youtube.com/watch?v=g_KBIEpljzY&t=510s) 装饰类的典型应用包括动态重载类的魔术方法,例如替换 cls.__str__ 以免去在各个类中重复手动编写字符串序列化逻辑。 - [10:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=600s) 面对任意复杂的嵌套装饰器,只需写出等价调用赋值链,就能立即推断出各层级所需的输入类型与输出类型。 - [10:20](https://www.youtube.com/watch?v=g_KBIEpljzY&t=620s) 类装饰器在工程中极具实用价值,如开源工具 object-print 即通过装饰类来自动实现类成员属性的友好打印与调试。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=0s) 概念澄清与类作为装饰器的核心机制

在上一期视频中,高天介绍了 Python 装饰器(decorator),并给出了一个简化版的定义:“这个装饰器就是一个输入是函数,输出也是函数的这么一个函数”。高天坦言,这个定义其实并不完全准确,当时只是为了让刚入门装饰器的人能够快速理解核心概念。随后许多观众通过留言或私信的方式,希望他深入讲解“类装饰器”。针对这个术语,高天明确指出“类装饰器”及其英文“class decorator”在理解上存在歧义:在某些语境下,它指的是“可以当作装饰器的类”;而在另一些语境下,它指的是“可以装饰类的装饰器”。这两个其实是正交的概念——一个谈论的是装饰器本身的形式,另一个谈论的是装饰器所修饰的对象。

```mermaid flowchart TD accTitle: 装饰器与被装饰对象的正交组合关系 accDescr: 装饰器形式分为函数与类,被修饰对象同样分为函数与类,彼此可两两正交组合。 subgraph S1["装饰器本身 (Decorator)"] D1["函数 (Function)"] D2["类 (Class)"] end subgraph S2["被装饰的对象 (Decorated Target)"] T1["函数 (Function)"] T2["类 (Class)"] end D1 -->|"可装饰"| T1 D1 -->|"可装饰"| T2 D2 -->|"可装饰"| T1 D2 -->|"可装饰"| T2 ```

为了厘清这些关系,高天强调:“所以你要弄懂的是它背后的核心机制和理念,而不是一个一个的组合去硬背。”许多教程往往要求初学者生搬硬套各种规则,但实际上只需要掌握一条核心的“万能公式”,即装饰器语法糖背后的真正等价形式:

$$\text{被装饰对象} = \text{装饰器}(\text{被装饰对象})$$

高天在代码编辑器中展示了一个名为 `Timer` 的计时器类,类中定义了接收一个 `func` 参数的 `__init__` 初始化方法,并将其保存在 `self.func` 中;同时定义了魔术方法 `__call__`。这个魔术方法的作用就是让该类的所有实例都变成一个 callable(可调用对象),能够像函数一样在后面加上圆括号进行调用。

当在普通的加法函数 `add(a, b)` 上方写上 `@Timer` 时,其语法糖背后的等价形式正是 `add = Timer(add)`。这意味着: 1. `Timer(add)` 实例化了一个 `Timer` 类的对象,并将原来的 `add` 函数作为参数传递给了 `__init__`,保存在 `self.func` 里。 2. 原来的函数名 `add` 现在指向了这个新生成的 `Timer` 对象。 3. 后续执行 `add(2, 3)` 时,本质上是调用了该对象的 `__call__(2, 3)` 方法。在 `__call__` 内部,记录开始时间,通过 `self.func(a, b)` 调用原函数并获取返回值,打印消耗的时间,最后返回计算结果。

打印 `type(add)` 也可以证实其类型就是一个 `Timer` 对象。高天总结道,原先的函数装饰器是“函数调用一个函数返回一个 callable”,而类作为装饰器则是“类调用一个函数返回一个 object,它依然是一个 callable”。

- `Timer.__init__(self, func)`:将原函数保存在 `self.func` 实例变量中。 - `Timer.__call__(self, *args, **kwargs)`:使实例成为 callable,负责包裹计时逻辑与原函数调用。 - 语法糖等价变换:`@Timer` 标注在 `def add():...` 之上,完全等价于 `add = Timer(add)`。

---

[05:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=300s) 带参数的类作为装饰器与装饰类的装饰器

当装饰器本身需要接收参数时,使用类来实现又该如何推导?高天提出了一个新需求:在打印耗时前输出一个可配置的前缀字符串 `prefix`,调用方式变为 `@Timer(prefix="curr_time")`。初学者面对带参数的类装饰器往往容易犯懵,但只要还原到万能公式的等价形式,整个推导就变得非常清晰:

$$\text{add} = \text{Timer}(\text{prefix}=\text{"curr\_time"})(\text{add})$$

从这个等价形式可以看出,程序首先执行 `Timer(prefix="curr_time")` 建立了一个 `Timer` 类的实例对象,紧接着对该对象进行调用并传入 `add` 函数,最后将调用的返回值重新赋给 `add`。因此,类的内部结构必须做相应改造: 1. `Timer` 类的 `__init__` 方法不再接收函数作为参数,而是接收配置参数 `prefix` 并保存为 `self.prefix`。 2. 实例的 `__call__` 方法在此时扮演了原先外层装饰器工厂的角色,它的入参必须是待装饰的函数 `func`。 3. 在 `__call__` 内部定义一个闭包包装函数 `wrapper`,在这个 `wrapper` 中执行计时、打印 `self.prefix` 以及调用 `func`,并将 `wrapper` 作为 `__call__` 的返回值。 高天演示了修改后的代码,运行后打印的前缀立刻变成了传入的自定义字符串。他指出,这种“可以成为装饰器的类”,在中文语境下称之为“装饰器类”更为贴切。

随后,高天转向了另一侧的正交概念——“真正的装饰类的装饰器”。在 Python 中,打印一个自定义类的实例通常只会输出默认的类名和内存地址等无用信息,除非在类中重载 `__str__` 方法。为了免去在每个类里手动编写的繁琐,可以编写一个装饰器 `add_str` 装饰在目标类 `MyObject` 上:

```python def add_str(cls): def __str__(self): return str(self.__dict__) cls.__str__ = __str__ return cls

@add_str class MyObject: def __init__(self, a, b): self.a = a self.b = b ```

面对类作为装饰目标的写法,高天再次强调使用等价形式来化解迷茫:“在 Python 中定义的 class 本身也是一个 object,那么第七行的 `@add_str` 就相当于第十四行的 `MyObject = add_str(MyObject)`。” - `add_str` 是一个接收类作为参数、同时返回一个类的函数(输入为 `cls`,输出为被修改后的 `cls`)。 - 装饰器在内部将 `cls.__str__` 替换成了返回 `str(self.__dict__)` 的新函数,从而打印出实例中的属性数据(例如 `{'a': 1, 'b': 2}`)。 - 如果装饰类的装饰器本身还需要传递参数,同样只需要利用等价公式外包一层返回函数的函数,其返回的函数签名依然保持“接收类并返回类”即可。

---

[10:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=600s) 等价形式的威力与实际应用场景

通过上述几个维度的推导,高天指出不论装饰器本身还是被修饰的对象到底是用函数还是用类实现,“只要你可以把它转换成等价形式,它都是比较好读的”。面对任何复杂的组合,只要写出等价形式,就能一眼看破本质:“而转换成等价形式之后,你就知道我需要写一个什么东西,它的输入应该是什么,它的输出应该是什么,就再也不用照着网上教程抄,然后还不知道对不对了。”

```mermaid flowchart LR accTitle: 装饰器编写决策推导流 accDescr: 展示遇到复杂装饰器时,通过写出等价形式来明确输入与输出类型的思考路径。 A["遇见复杂装饰器需求"] --> B["写出语法糖的等价赋值语句"] B --> C{"分析调用链条"} C --> D["确定各层输入参数类型 (函数/类/配置项)"] C --> E["确定各层返回值类型 (callable/object/class)"] D & E --> F["快速写出结构严谨的实现"] ```

针对部分人可能产生的“这么偏门的东西在实际中到底有没有用”的疑问,高天给出了非常肯定的回答:“当然有用了。”他以自己开源编写的实用工具库 `object-print` 为例,其中就包含了一个名为 `add_object_print` 的类装饰器。开发者只需在任何自定义类前加上 `@add_object_print`,就可以在调用 `print()` 时自动输出格式化后的完整类结构以及内部成员变量(例如对象名 `MyObject` 及其成员变量 `a = 1, b = 2`),大大简化了日常调试和 Debug 的成本。最后,高天向提供字幕支持的志愿者杨茜茜表达了感谢,并提醒观众点赞、关注与“一键三连”。

## Notable quotes > [10:00](https://www.youtube.com/watch?v=g_KBIEpljzY&t=600s) 转换成等价形式之后,你就知道我需要写一个什么东西,它的输入应该是什么,它的输出应该是什么,就再也不用照着网上教程抄,然后还不知道对不对了。 — 高天

## Mentioned 高天, Python, object-print, 杨茜茜

## Video description 这期视频在之前一期装饰器的基础之上,又引入了类作为装饰器,和装饰类的装饰器。各种复杂的组合,只需要记住一个公式,就全都解决了!

[Source](https://www.youtube.com/watch?v=g_KBIEpljzY)

中文简报

码农高天拆解Python装饰器 统一语法糖等价公式消解类嵌套分歧 任何复杂装饰器结构均遵循被装饰对象等于装饰器调用的赋值逻辑,明确每层调用的输入输出即可彻底终结死记硬背。 高天首先澄清了“类装饰器”术语在语境上的歧义,指出装饰器本身的形式与被装饰对象是正交关系,并提炼出核心万能公式“被装饰对象 = 装饰器(被装饰对象)”。随后,他演示了如何利用类的 `__init__` 与 `__call__` 魔术方法实现普通计时器,并通过等价形式推导带参数的类装饰器内部结构改造。最后,视频讲解了如何编写直接装饰类的装饰器(如动态重载 `__str__`),并结合其开源项目 `object-print` 证明了该模式在实际开发与调试中的巨大实用价值。 “类装饰器”(class decorator)存在歧义,它实际上涵盖了两个正交维度:“可以作为装饰器的类”与“装饰类的装饰器”,两者与普通函数可以自由交叉组合。 (0:00) 理解复杂装饰器的核心在于还原语法糖的等价形式“被装饰对象 = 装饰器(被装饰对象)”,通过明确各层调用的输入与输出类型来推导代码结构,避免死记硬背。 (1:00) 用类实现带参数装饰器时,类的 __init__ 负责接收配置参数,而 __call__ 方法则作为工厂接收被装饰函数并返回闭包包装函数 wrapper。 (5:00) 在 Python 中类本身也是对象,装饰类的装饰器只需接收目标类对象,动态为其绑定或重写魔术方法(如 __str__)后再返回该类即可生效。 (7:30)

Reading summary

针对 Python 中“类装饰器”术语的歧义与复杂语法,码农高天指出装饰器与其修饰对象在形式上呈正交关系;只要掌握“被装饰对象 = 装饰器(被装饰对象)”这一万能等价公式,就能轻松推导和实现各类复杂的装饰器逻辑。 概念澄清与正交关系 (0:00) 高天指出,“类装饰器”(class decorator)在语境中常存在双重含义:一是指“作为装饰器的类”,二是指“修饰类的装饰器”。两者属于正交维度,即装饰器本身可以是函数或类,被修饰的目标对象同样可以是函数或类,开发者无需死记硬背两两组合的特例规则。 破解所有组合的核心在于还原装饰器语法糖背后的等价形式,即“被装饰对象 = 装饰器(被装饰对象)”。只要写出等价调用语句,就能明确每一层调用所需的输入参数类型与返回值类型。 类作为装饰器的运行机制 (0:00) 当使用类作为装饰器且不带参数时(如 `@Timer` 装饰 `add` 函数),其等价形式为 `add = Timer(add)`。在此模式下,类的 `__init__(self, func)` 方法接收并保存被修饰的函数,而类中实现的魔术方法 `__call__` 则使实例成为 callable 对象。后续执行 `add()` 本质上是在调用该实例的 `__call__` 方法,从而完成计时并执行原函数逻辑。 当类装饰器需要接收配置参数时(例如 `@Timer(prefix="curr_time")`),等价形式转化为 `add = Timer(prefix="curr_time")(add)`。此时代码执行链发生改变:`__init__` 负责接收配置参数 `prefix`;实例的 `__call__` 方法则接收待装饰函数 `func`,并在其内部定义并返回闭包函数 `wrapper` 以执行核心逻辑。高天认为此类结构在中文语境下称作“装饰器类”更为贴切。 修饰类的装饰器及工程应用 (5:00) 在 Python 中,类本身也是一个对象(object),因此同样可以使用装饰器进行修饰。例如通过 `@add_str` 装饰目标类 `MyObject`,其等价形式为 `MyObject = add_str(MyObject)`。装饰器函数接收目标类 `cls` 作为参数,通过重载或动态添加 `__str__` 方法来输出实例的属性字典 `self.__dict__`,最后返回修改后的 `cls`,从而避免在每个类中重复编写代码。 针对此类用法是否具备实际价值的疑问,高天以其开源的 `object-print` 库为例说明:通过向自定义类添加 `@add_object_print` 装饰器,即可在调用 `print()` 时自动输出结构化的类名与成员变量内容,有效降低了日常调试成本。 掌握装饰器的等价赋值公式能够彻底摆脱机械记忆规则的困扰,开发者只需根据转换后的调用链分析输入与返回值,即可规范构建包括带参类装饰器和类修改器在内的各类进阶工具。

Source quotes

[10:00] 高天: 转换成等价形式之后,你就知道我需要写一个什么东西,它的输入应该是什么,它的输出应该是什么,就再也不用照着网上教程抄,然后还不知道对不对了。

Original transcript

[0:00] 在之前一期视频里我们介绍过装饰器 也就是decorator在那期视频里呢 我说这个装饰器就是一个输入式函数 输出也是函数的这么一个函数这个定义其实它并不完全准确这么说只是让刚入门装饰器的人可以理解它的核心理念那在那期视频的留言里就有人让我讲一下类装饰器那包括后面有很多人通过留言或者私信的方式让我讲一讲这个类装饰器啊所以这期视频我们就讲一下装饰器和类之间的爱恨纠葛那类装饰器这个词包括它的英文class decorator实际上是有一定的奇异的在有的地方 它是指可以当作装饰器的类那在有些地方呢 它是指可以装饰类的装饰器这其实是两个证交的概念一个聊的是装饰器本身一个聊的是装饰器要装饰的对象那么通过今天的视频 你就可以知道 装饰器本身既可以是函数也可以是类而装饰的对象同样既可以是函数也可以是类所以你要弄懂的是它背后的核心机制和理念而不是一个一个的组合去应备好 我们看左边的代码啊 我定义了一个timer类然后这个timers有一个init函数这个init函数take一个argument是一个function然后把它保存在自己的这个object里接下来呢我还定义了它的一个魔术方法是call那可能有的人对他熟悉 有的人对他不熟悉有关魔术方法呢未来我们有机会认真讲但是这个靠就是让所有这个类的实力都变成一个callable简单的理解就是这个类的实力这个类的对象都可以当作函数用你都可以调用它在后面画一个小括号然后我把timers这个类本身当做一个装饰器装饰在了这个 add 函数上这个ad函数很简单啊 就是返回A+B那这个装饰器呢跟我们上一个视频里的例子其实是一样的我们先看一下预行结果可以看到它依然是先打印出来了这个函数需要的时间然后在第17行打印出来这个函数的返回值那问题来了这个timer类究竟是怎么作为一个装饰器起作用的呢那可能很多教程就要让他背了对吧一个类作为装饰器要怎么样一个函数要怎么样后面加了argument要怎么样但是今天我教给你 你只需要记住一条万能公式这些东西你全都不用背就是这个装饰器与法堂背后真正的等价形式也就是13行这个at timer究竟做了什么而在上期视频里面 这件事我讲过在一个函数上做装饰器就等价于这个函数名等于装饰器调用这个函数也就是说 第13行的at timer他就相当于在后面运行了一个add=timer那如果你能把它转换成这个形式的话你是不是就立刻可以明白到底发生什么事了这个装饰器相当于把爱的从一个函数变成了一个timers类的对象因为我做了一个timers addtimer是一个类 对不对 它相当于建立了一个这个类的对象然后add被传进了它的annit函数里init函数take一个funk 对不对所以add作为一个参数被保存在了self.func里然后整个这个TimerObject被保存在了Add这个名字里那接下来第20行 add 2 3的时候它实际上是调用了TimerObject的call这个函数它相当于做了一个对象的调用啊那这个时候呢2跟3就被传到了这个call里面所以他就运行了start等于时间然后拿到一个return valueself.func就是原来的add函数对不对他就把a跟b加起来然后返回到了write里然后打印了消耗的时间再返回这个值最后这个值再被print出来可以看到它跟我们之前讲的函数decorator其实没有很大的区别原来的函数装饰器是函数调用一个函数返回一个函数更确切的说是函数调用一个函数返回一个callable那现在是类调用一个函数返回一个object 它依然是一个callable你只需要把Decorator在脑海中转化成下面这个等价形式你就非常容易理解了我们甚至还可以在这里打印一下add的type 可以看到add就是一个timer object好 那我们之前讲过装饰器本身也可以有argument 对不对假如说我们举个例子 [5:00] 之前我们打印的不是time_ 然后这个函数使用的时间吗现在我想让time_这个部分变成可变的也就是我想让第13行变成这个样子这个timers本身呢我还想给它传几个参数prefix让他在打印的时候呢先打印我给进去的这个street然后再打时间我们在上一期视频里面聊过函数的装饰器怎么完成这个事对不对那用类作为装饰器的时候怎么完成这个事呢你现在可能又有点发懵对吧那就返回到我们的那个万能公式把它写成它的等价形式那第13行的那个装饰器呢现在等价于第18行的这项代码我们可以看到add=timer/prefix=currtime然后再括号 add也就是说我们首先建立了一个TimerObject然后调用这个object把add作为一个参数传进去再把调用的返回值保存到Ad里那我们对应的类该怎么改呢首先Timer作为一个class 它的参数就不再是一个函数了 而是prefix接下来当我们调用这个timers object的时候 我们希望它的输入是一个函数 返回值也是一个函数因此这个call method它的参数肯定是function对不对然后它里面要再定义一个函数 这个函数会成为我们的返回值而这个函数里面就是刚才我们的计时运行传进来的这个参数funk然后再打印消耗的时间那这个部分呢 长得就跟我们之前写的函数装饰器很像那其实只要你一步一步推从这个timers prefix返回了一个object然后他再做了一个call 然后add传进function 返回的是一个rapper 存到add里面这么一路下来其实逻辑就会变得非常顺当然了现在只是结构是对的那为了让这个程序的功能也正确了我们还需要把time号给改成self.prefix我们看我们把prefix加上去 然后运行一下这条代码大家可以看到前面打印的部分就变了好 那我们介绍了这个可以成为装饰器的类呢其实用中文的语境讲 它更像装饰器类而不是类装饰器那接下来呢 我们就介绍一下真正的类对了装饰器好 我们来看左边这段代码啊现在第7行在一个class的definition上面我给加了一个装饰器那我们知道在python里面一个自定义的class的对象当你print的时候它是不会打印出任何有价值的信息的它只会告诉你这个对象是一个什么类那我们可以通过重载它的下方线下方线strain函数来改变它print出来的结果当然了 如果每一个对象都改的话 也挺麻烦的所以我就写了这么一个简单的装饰类的装饰器我们先看一下效果大家可以看到我打印出来的东西是A是1 B是2这个也就是o也就是my object的对象里面的数据这时候你可能会想了 对吧 装饰器能给函数用 怎么还能给class用呢它们之间有什么不同吗每当你遇到装饰器感到迷茫的时候你只需要把它换成它的等价形式我们来换一下我们知道在python中定义的class本身也是一个object那么第七行的add string就相当于14行的my object等于add string my object也就是add string这个函数是一个参数式class返回值也是class的函数那我们再往上看这个addString的definition它的参数是一个classcls对不对它的返回值是原封不动的返回这个class只不过他在第四行的时候把class的string函数给替代成了他自定义的string函数而它定义的这个string函数是返回self. 下文下文线的string形式也就是说这个decorator本质上就是重载了一下这个class的下方线下方线的string函数那如果你能把它成功的想象成这个等价形式的话你就会发现给类写装饰器也没有什么难的嘛他就是输入一个类返回一个类嘛然后我在这个类上动动手脚完成我需要的事情就可以了当然同样的这个装饰器也是可以传参数的如果你弄不懂的话依然是把它写成等价形式然后再去想 我这个函数的返回值应该是一个什么那样的话 这个函数的返回值就应该是一个函数了而返回的函数参数是一个class 返回值是一个class那通过今天这几个例子大家应该可以感受到 [10:00] 是Decorator本身还是他装饰的对象不管他们是函数还是累只要你可以把它转换成等价形式它都是比较好读的而转换成等价形式之后你就知道我需要写一个什么东西它的输入应该是什么 它的输出应该是什么就再也不用照着网上教程抄然后还不知道对不对了当然你可能会问这么偏门的东西有用吗当然有用了在我写的 object print这个包里就有一个decorator叫做 add object print那对于任何你自定义的类 你只需要把这个装饰器装饰到这个类上你就可以把这个类的信息用print给正常的打印出来了我们运行一下大家可以看到他就打印出来这个O是一个myobject的对象然后它里面两个member variable a是1 b是2那这种用法对于debug来说是相对来说比较简单的因为你基本上只需要给你的class加一个decorator就OK了好 那今天的视频就到这里 希望对大家理解装饰器有所帮助 如果喜欢的话 记得点一下关注 别忘了一键三连哦字幕志愿者 杨茜茜