TubeSum ← Transcribe a video

Python Decorators: The Complete Guide

Transcribed Jun 14, 2026 Watch on YouTube ↗
Intermediate 14 min read For: Python developers familiar with functions and classes who want to understand decorators.
194.2K
Views
7.9K
Likes
267
Comments
75
Dislikes
4.2%
🔥 High Engagement

AI Summary

This video explains how to use Python decorators to solve cross-cutting concerns like logging, authentication, and benchmarking. It starts with the classic decorator design pattern, then shows Python's simpler function-based approach, and discusses when to use decorators.

[0:00]
Cross-cutting concerns

Logging, authentication, and benchmarking are hard to integrate without code duplication or coupling.

[2:19]
Classic decorator pattern

Uses abstract component and decorator classes; decorators wrap components and can be stacked.

[9:42]
Python function decorators

Python's first-class functions allow simpler decorators: a function that takes a function and returns a wrapped function.

[14:58]
@ syntax sugar

Using @decorator above a function definition is syntactic sugar for applying the decorator.

[17:55]
Preserving function metadata

Use functools.wraps to copy the original function's name and docstring to the wrapper.

[19:07]
Decorators with arguments

Add an outer function that takes arguments and returns the actual decorator; or use functools.partial.

[24:15]
Potential issues

Too many decorators reduce readability; decorators that change function signatures break type checking.

Python decorators elegantly handle cross-cutting concerns but should be used judiciously to maintain code readability and type safety.

Clickbait Check

90% Legit

"Title is accurate; video thoroughly covers Python decorators from basics to advanced usage."

Mentioned in this Video

Tutorial Checklist

1 9:42 Identify a cross-cutting concern like logging or benchmarking.
2 10:09 Write a plain function for the core behavior (e.g., count_primes).
3 12:26 Create a decorator function that takes a function and returns a wrapper function.
4 13:19 Inside the decorator, define a wrapper function with *args and **kwargs.
5 13:41 In the wrapper, call the original function with the passed arguments and add extra behavior.
6 14:11 Return the wrapper function from the decorator.
7 14:58 Apply the decorator using @decorator_name above the function definition.
8 17:55 Use @functools.wraps(func) on the wrapper to preserve metadata.
9 19:07 For decorators with arguments, add an outer function that takes arguments and returns the decorator.

Study Flashcards (7)

What are cross-cutting concerns?

easy Click to reveal answer

Features like logging, authentication, and benchmarking that affect multiple parts of a codebase.

What is the classic decorator design pattern?

medium Click to reveal answer

A structural pattern where decorator objects wrap components to add behavior, using an abstract component class and concrete decorators.

2:54

How do Python function decorators work?

easy Click to reveal answer

A decorator is a function that takes a function and returns a new function (wrapper) that adds behavior.

12:26

What does @functools.wraps do?

medium Click to reveal answer

It copies the original function's name, docstring, and other metadata to the wrapper function.

17:55

How do you create a decorator with arguments?

hard Click to reveal answer

Add an outer function that takes the arguments and returns the actual decorator function.

19:07

What is a potential problem with using many decorators?

medium Click to reveal answer

It can reduce code readability because the order and effect of decorators may be unclear.

24:15

Why should decorators avoid changing function signatures?

medium Click to reveal answer

Changing signatures breaks type checking and reduces code clarity.

25:13

💡 Key Takeaways

💡

Cross-cutting concerns defined

Clearly explains the problem decorators solve.

🔧

Python's simpler approach

Shows how Python's first-class functions simplify the decorator pattern.

9:42
🔧

@ syntax sugar

Demonstrates Python's syntactic sugar for decorators.

14:58
🔧

Preserving metadata with functools.wraps

Important practical tip for correct decorator behavior.

17:55
⚖️

Readability trade-off

Warns against overusing decorators, a key design principle.

24:15

✂️ Creator Tools: Viral Hooks

AI-generated clip ideas for Shorts based on the transcript

横切关注点:日志与认证的痛点

45s

直击开发者日常痛点,引发共鸣,吸引观众继续观看解决方案。

▶ Play Clip

装饰器模式 vs Python装饰器

50s

对比经典设计模式与Python语法糖,展示Python的优雅,激发学习兴趣。

▶ Play Clip

用函数实现通用基准测试

50s

从具体到通用,展示如何编写可复用的装饰器,实用性强,观众可直接应用。

▶ Play Clip

装饰器参数传递技巧

50s

解决实际开发中装饰器需要参数的问题,提供两种优雅方案,提升代码灵活性。

▶ Play Clip

装饰器的陷阱:可读性与签名

50s

揭示装饰器的潜在问题,提供实用建议,帮助观众避免常见错误,内容深度高。

▶ Play Clip

[00:00] 如果您已经开发软件一段时间了,您会

[00:02] 注意到某些类型的功能

[00:04] 确实很难很好地集成到您的代码中,而不

[00:08] 导致大量重复或大量额外耦合,例如

[00:11] 日志记录。所以你有一个函数,你想要记录一些东西。

[00:15] 因此,只需添加一堆记录所有内容的行即可。您是否

[00:19] 在代码中的任何地方都这样做,从而导致大量额外的

[00:21] 代码行?如果您有一个记录器对象,您会导入它吗?然后

[00:25] 到处使用它导致耦合?或者您是否

[00:28] 为每个需要记录的函数(即

[00:31] 记录器对象)添加一个额外的参数,这样您就可以在任何地方获得额外的参数。另一个

[00:35] 例子是身份验证。因此,您的代码中可能有一些端点

[00:39] ,您希望确保实际上

[00:42] 允许用户调用这些端点。 那么,您是否只需

[00:46] 为每个需要它的功能 复制粘贴

[00:49] 该身份验证代码? 然后你有很多代码重复?或者将其放入

[00:52] 一个单独的函数中,然后必须

[00:56] 在各处导入和调用,这会增加耦合和重复。所以

[00:59] 这些东西也被称为横切关注点,因为

[01:03] 它们基本上遍布你的代码。 除了登录身份验证之外, 还有几个

[01:06] 这样的例子,例如

[01:08] 通过事件进行基准测试或用户行为跟踪,基本上,

[01:12] 这些事情是课堂上的巨大痛苦。

[01:15] 那么如何解决呢?好吧,一个可能的解决方案是使用

[01:19] 装饰器执行经典的面向对象装饰器设计

[01:23] 模式,但在 Python 中,您实际上不应该使用它,

[01:26] 因为它可以更优雅地解决问题。所以今天,

[01:30] 我将向您展示如何在 Python 中做到这一点。我还

[01:33] 对是否应该使用装饰器有一些想法,

[01:36] 因为,有两个问题需要注意

[01:40] 。现在,在我们开始之前,我有一些东西要给你。如果您想

[01:44] 更好地分析代码并检测

[01:48] 需要解决的问题,我创建了一个免费的

[01:51] 代码诊断研讨会,您可以通过访问

[01:54] arjan.codes/diagnosis 加入该研讨会。它是完全免费的。这是一个研讨会

[01:59] ,解释了一个三因素框架,以帮助您

[02:02] 更好地决定应该在哪里以及如何改进

[02:05] 代码。为了说明如何做到这一点,我看一下一些

[02:09] 现有的软件包,您

[02:13] 目前可能在自己的项目中实际使用这些软件包。所以 arjan.codes/diagnosis

[02:15] 加入免费研讨会。现在,让我们深入了解

[02:19] 装饰器。经典装饰器设计模式的想法

[02:23] 是,您基本上采用一个对象,并且想要

[02:26] 向其添加一些行为,我将向您展示一个简单的示例,说明

[02:30] 其工作原理。所以我这里有一个函数是 prime ,它基本上

[02:34] 检测一个数字是否是素数,该算法非常

[02:38] 基本。如果您有,特别是如果您有更大的素数,

[02:41] 那么这不是您想要使用的算法类型

[02:43] 。还有其他更好的算法,我不会

[02:46] 在本视频中提及。但基本上,它检查一个数字是否

[02:49] 是素数,我将使用这个函数来向您展示

[02:51] 如何装饰一个实际有效的模式。现在

[02:54] 装饰器模式有两个重要的部分。有一个

[02:57] 组件,它基本上是具有某种

[03:00] 行为的类。然后还有一些装饰器对象,它们可以

[03:04] 向该组件添加行为,也可以向其他

[03:08] 装饰器添加行为。因此,我在这个示例中设置了一个非常基本的类结构,

[03:12] 以便快速向您展示该模式是如何工作的。

[03:15] 有一个抽象组件类,它将成为

[03:18] 其他所有内容的超类。它有一个执行方法,

[03:21] 它返回一个整数并获取一个上限。

[03:24] 我将在这个执行方法中做的 事情

[03:27] ,这就是我们 在具体组件中的位置,它将确定

[03:31] 范围内直到上限的每个数字是否

[03:34] 是素数。所以这当然是素数函数,

[03:38] 然后只计算这些数字。这就是它

[03:41] 返回的内容。结果,这就是执行的作用。再说一遍,这个

[03:45] 这不是你在 Python 中设置它的方式,我只是想向

[03:47] 你展示装饰器模式工作的基本方式。这

[03:51] 就是我们的组成部分。这就是它的行为。 装饰器 模式

[03:54] 所做的是你没有的,这就是

[03:57] 我这里所拥有的,一个抽象装饰器,它完成了

[04:00] 抽象组件的子类。这就是这堂课。这

[04:04] 添加了一个初始化程序,它本身作为参数,一个

[04:08] 抽象组件,并将其存储在一个修饰的

[04:13] 实例变量中。所以装饰器引用了许多

[04:17] 组件。由于装饰器本身也是一个

[04:20] 组件,因此该引用也可以是另一个装饰器。这样

[04:23] 你就可以添加一系列的东西了。我

[04:27] 创建了一个装饰器示例,它是一个基准

[04:31] 装饰器。所以当然需要有

[04:35] 上限的执行方法返回整数,但是我要做的是使用

[04:39] 出生计数器函数来记录开始时间。然后,我

[04:43] 通过在我们有引用的装饰对象上调用执行来计算该值

[04:48] ,我记录酶,确定

[04:52] 运行时是什么,然后记录一些

[04:55] 有关该信息的信息,然后返回该值。所以它

[04:58] 基本上所做的就是它包装了

[05:00] 来自自点装饰实例变量的原始执行方法

[05:04] 。这就是基准装饰器。然后

[05:08] 运行此代码的方法就是这样,我

[05:11] 在这里初始化了一些日志记录,但随后我创建了该组件。这就是

[05:15] 这里的组件,它具有

[05:18] 确定有多少个素数 的执行方法

[05:21] ,然后我 创建一个基准装饰器,它将组件作为

[05:26] 参数。最后,我执行基准

[05:30] 装饰器,我可能不应该调用这个组件,

[05:32] 因为这有点令人困惑。因此,让我们将其重命名为

[05:36] 基准装饰器,如下所示。然后在这里,我还将

[05:42] 调用基准装饰器上的执行。所以这里发生的事情

[05:45] 是我用数字 100k 调用执行,在这种情况下,所以

[05:51] 这将运行基准装饰器点执行步骤,

[05:54] 因为开始时间依次调用自学

[05:58] 装饰器,执行,它会执行此操作,然后它返回

[06:01] ,并锁定最后的信息。所以当

[06:05] 我运行这个时,这就是我们得到的,我们得到日志信息。

[06:09] 显然,这花了 0.08 秒。我们还可以存储一个

[06:13] 值。

[06:16] 然后我们也打印该值。

[06:19] 现在我们看到 9592 个素数低于 100,000。

[06:25] 装饰器设计模式的有趣之处在于,

[06:27] 您现在可以向这些额外的装饰器添加额外的层,例如

[06:31] ,假设我想创建一个名为

[06:34] 日志装饰器的类,我想做更多的日志记录,我们总是想做

[06:38] 更多的日志记录。这也是一个抽象装饰器。它

[06:42] 会有一个执行方法,就像其他

[06:47] 装饰器一样,对吗?所以这段代码并不是我们想要的。但我们

[06:51] 想做的更简单,我们要

[06:55] 在这里打印一些日志信息,比如调用,假设我们要调用

[06:59] self 点,装饰它。

[07:02] 类名。这就是我们想要打印的内容,就像这样。

[07:09] 我将复制该行。

[07:15] 就像这样。现在我要写 value equals self dotdecorated

[07:22] dotexecute。我们将传递它的上限

[07:26] ,就像这样。当然,最后我们会返回该值。

[07:31] 现在我们能做的就是回到主函数,我们

[07:35] 可以将日志记录添加到组件和装饰器的序列中。因此,

[07:38] 如果我现在添加一个日志装饰器,它是一个

[07:44] 获取组件的日志装饰器对象,就像这样。

[07:49] 我没有将组件传递给基准装饰器,而是将 日志

[07:52] 记录装饰器传递给基准装饰器。现在我们

[07:57] 有了一个由日志记录装饰器包裹的组件,

[08:00] 该组件又由基准装饰器包裹。因此,当我

[08:04] 运行此命令时,您会看到我们现在

[08:07] 在这里获得额外的日志记录信息。您还可以看到这是由我们的日志记录

[08:12] 组件生成的。在这里我们看到基准装饰器是愚蠢的

[08:17] 调用登录装饰器的执行。 这就是 我们得到的

[08:21] 序列 。 显然,由于

[08:23] 我们正在执行额外的类结构和登录,因此需要更长的时间,

[08:26] 即 0.09 秒。而且切换也相对容易,

[08:30] 因为也许您不想登录成为

[08:33] 基准测试的一部分,对吧。所以在这种情况下,我们要做的

[08:36] 是,复制这一行并将

[08:40] 其移到此处,然后将其删除。现在我要做

[08:45] 的是基准装饰器将获取组件

[08:49] 。日志装饰器将获得

[08:54] 基准装饰器。 所以我们实际上并没有测量

[08:57] 登录某项内容需要 多 长时间。

[09:00] 但我们实际上是 在测量确定某物是否为

[09:02] 素数 的执行时间 。 然后我们所做的就是调用记录装饰器

[09:07] 点执行,然后当我们运行这个数字时,我们

[09:11] 仍然得到 0.09 秒,这可能与 CPU 正在执行的其他任务

[09:15] 有关 。 或者可能还有其他一些

[09:18] 方面导致速度变慢,例如

[09:21] 调用堆栈中的额外深度,诸如此类。但现在记录是以

[09:24] 不同的顺序完成的。首先,我们编写日志,然后调用

[09:26] 基准装饰器,然后执行具体组件的方法

[09:30] 。所以有点不同。

[09:33] 装饰器模式的有趣之处在于,您基本上可以 按照

[09:35] 您喜欢的任何方式确定顺序。它

[09:39] 也会影响应用程序的行为。

[09:42] 现在我设置的方式实际上纯粹是遵循

[09:45] 装饰器设计模式的经典版本,该模式基本上是

[09:49] 为不支持

[09:53] 仅接受类的函数的语言而设计的,然后这就是我们必须这样做的方式,

[09:55] 因为Python对函数有很好的支持,

[09:58] 而不是有这个复杂的类。

[10:00] 结构中,我们可以简单地将一个函数包裹在很多

[10:03] 函数中。换句话说,只需让一个函数调用另一个

[10:06] 函数即可。那么它实际上是如何运作的呢?好吧,让我们首先

[10:09] 删除这里的这些类层次结构。我将把

[10:13] 这里的执行方法改为一个函数。我们

[10:18] 也将其称为质数,就像这样。 当然,不再需要 自我

[10:23] 了。现在 我们有了这些非常简单的

[10:27] 功能。那么我们如何

[10:30] 通过基准测试来装饰素数计数呢?嗯,我们能做什么。所以让

[10:33] 我也删除这个类,我们能做的就是将这个

[10:37] 基准装饰器也更改为函数。所以我要删除

[10:42] 这里的类名。这将是一个

[10:45] 称为基准的函数,基准得到一个上限,因此它

[10:49] 具有与计数素数完全相同的签名,然后,

[10:53] 它所做的是计算开始时间并作为一个值,它

[10:58] 确实计数像这样的素数,它计算结束时间,

[11:02] 然后打印日志信息。在这里,我们可能想

[11:06] 改变它来计算素数,就像登录

[11:12] 装饰器一样,我现在只是注释掉,我们

[11:15] 稍后再添加回来。现在如果我们想使用这个,我们需要

[11:18] 做的是 value equals,然后我简单地调用 benchmark,嗯,

[11:22] 实际上是 self,我们显然还应该在这里删除。

[11:25] 现在为了运行这段代码,我需要做的就是

[11:28] 称为基准测试,然后我只需通过上限即可。

[11:33] 让我们重新添加一些日志记录。

[11:40] 就像这样。当我运行这个时,我们得到的就是

[11:44] 我们之前得到的。哦,我们也回到了 0.08 秒。

[11:48] 现在,不太好的事情是,当前的基准测试直接

[11:52] 依赖于当前的素数,我不能使用

[11:55] 这里的基准函数来对其他函数进行基准测试。所以

[11:59] 这不太好,对吧?因为现在如果我想对

[12:02] 另一个函数进行基准测试,我基本上必须复制粘贴

[12:04] 此代码,然后 用需要进行基准测试

[12:07] 的函数替换此函数 。 所以这不太好。因此,

[12:11] 我们可以做的就是让它更通用一些,让基准测试

[12:15] 得到一个它应该调用的函数。但后来我们

[12:18] 仍然存在问题,因为基准测试需要知道

[12:20] 该函数的参数是什么,然后传递

[12:23] 这些参数。但我们稍后会讨论这个问题。因此,

[12:26] 我们可以做的不是直接依赖于计数素数来进行基准测试,

[12:29] 而是让基准测试获得一个它 应该进行基准测试

[12:32] 的函数 ,对吧。 假设这是一个 COBOL。

[12:36] 现在我们可以做类似的事情,它获取一个 int,然后

[12:40] 返回一个 int,对吧?但是,当我们将素

[12:43] 数更改为该函数时,我们仍然需要以某种方式提供

[12:46] 上限,但我们没有。因此,还有另一种

[12:48] 方法可以使其更加通用,我们可以做的

[12:52] 是,基准测试不会获取该函数并返回一个

[12:55] 整数,但它实际上会获取一个带有签名的函数

[12:59] ,而我们并不真正关心它是什么是。所以基本上,它的

[13:02] 类型是any,并且它还将返回另一个函数。

[13:07] 所以这也是一个可调用的。

[13:10] 所以它具有完全相同的签名。因此,进行基准测试,获取一个

[13:13] 函数,以某种方式修改它,然后返回该

[13:17] 函数。这个函数是什么样的?嗯,它

[13:19] 基本上包含了这段代码。我们将其称为包装

[13:24] 函数。该包装函数有参数。

[13:29] 它有关键字参数。所以这基本上可以是

[13:33] Python 中的任何函数,对吗?它会返回任何内容,任何带有

[13:38] 大写字母的内容,就像这样。然后在这里,在值中,我们

[13:41] 实际上调用作为参数获得的这个函数。 我们传递 什么

[13:45] 好吧,我们传递参数,我们传递

[13:49] 关键字参数,就像这样。然后我们不将此计数称为

[13:53] 素数,而是将其称为类似

[13:56] func 点名称的东西。因为这基本上就是我们正在做的事情。

[14:01] 正确的?因此,我们有一个包装函数,它包装了

[14:05] 我们传递的原始函数,然后我们简单地返回该

[14:11] 包装函数。所以现在基准是通用的。它得到一个

[14:15] 函数。我们在内部定义一个包装函数,调用该

[14:19] 函数进行日志记录。最后,我们返回那个

[14:23] 包装的函数。这里是如何运作的?好吧,在这里,

[14:26] 我们需要做的是获取

[14:30] 返回包装函数的基准测试,我们在这里将其称为包装函数。

[14:33] 我们需要将它传递给我们想要包装的函数。

[14:35] 这就是素数的计数。然后这个值

[14:40] 实际上就是我们调用的包装函数。然后我们将

[14:44] 其传递给上限,就像这样。现在让我们运行这段代码。您

[14:49] 会看到我们得到了与以前完全相同的结果,但现在它

[14:52] 真的很好。该基准基本上可以接受

[14:55] 我们喜欢的任何功能。现在,Python 中真正有趣的是,

[14:58] 实际上有一种更简单的方法,

[15:00] 即使用 out 表示法来执行此操作。因此,函数不是调用

[15:05] 提供的基准测试,而是返回另一个函数。然后

[15:09] 调用该函数,有一个更简单的方法来做到这一点,

[15:11] 那就是我们有我们的计数素数。我只是

[15:15] 将其移至下方以使其更容易理解。然后

[15:20] 我们在这里所做的就是简单地编写基准测试。因为

[15:24] 我们将其添加为装饰器来计算素

[15:27] 数,所以这意味着我们

[15:30] 在这里创建包装函数时所做的所有行为都不会

[15:33] 自动为我们完成。所以我们可以做的就是我们

[15:37] 现在可以简单地调用值是计数素数。我们不再需要这个

[15:43] 代码了。现在当我运行这个时,我们将得到完全相同

[15:47] 的结果。所以Python中的这个加号基本上是

[15:51] 语法糖,用于将行为包装在另一个

[15:55] 函数周围。这就是为什么这被称为装饰器,因为

[15:58] 原则上与装饰器设计模式的思想相同。

[16:02] 我们可以对日志做同样的事情。所以让我复制

[16:05] 这段代码。我将在这里使用它。我们称之为

[16:10] 日志记录。然后在包装函数中,我们将

[16:15] 拥有这里的代码。

[16:22] 当然,没有自点装饰器。所以我们只是简单地

[16:25] 调用 func 点,然后调用美元名称,所以它会打印

[16:31] 函数的名称。当然,这里我们也用参数

[16:34] 调用函数。

[16:40] 对于关键字参数,我们返回结果值。

[16:44] 在这里,这也成为函数点名称。因此,它的工作

[16:49] 方式与我们这里的基准函数完全相同

[16:52] 。现在我们可以做的是我们可以

[16:55] 在这里添加第二个带有日志记录的装饰器。好的一点是,我们只需

[17:00] 在这里选择退出,我们不必更改主函数中的任何内容,

[17:03] 因为现在当我们运行它时,您会看到我们也在记录

[17:06] 它,顺便说一句,如果您到目前为止很喜欢这个视频,给

[17:09] 它点个赞,这是装饰这个视频的好方法。它还

[17:13] 帮助我在 YouTube 上吸引更广泛的受众。现在让我们回到

[17:17] 这个例子,看看其中的一些问题,因为如果

[17:20] 我们看看日志记录实际上做了什么,它会记录函数的名称

[17:23] ,这是一个包装器,但这并不是

[17:26] 我们真正想要的。因为wrapper就像是

[17:30] benchmark的一个内部函数名。所以我们并不真正关心我们只是

[17:33] 想写出我们知道发生了什么的实际函数名称

[17:38] ,日志实际上是有用的,对吧?那么

[17:41] 我们该怎么做呢,因为我们这里没有这些信息。通过

[17:44] 日志记录,我们只知道有一个函数

[17:47] 作为参数,我们没有任何其他信息。所以

[17:50] 仅仅像这样包装函数是行不通的。但

[17:52] 幸运的是,有一个非常简单的解决方案。这来自

[17:55] func 工具包。那么让我导入

[18:00] func 工具。化石有一个非常好的装饰器,称为

[18:04] 包装。这解决了名称问题。现在要做到这一点,

[18:09] 我们需要进入我们的装饰器进行基准测试,然后我们

[18:13] 只需将 funk 添加到点包装中。

[18:19] 然后我们为其提供它所包装的函数。

[18:23] 让我们也面对这个问题,因为当然,我们也有

[18:26] 同样的问题。所以现在当我运行这个时,你会发现它

[18:31] 实际上引用了到处的议会素数,这

[18:35] 当然更有意义。我在

[18:38] 开始时提到装饰器可以是一个有用的工具,可以帮助解决

[18:41] 这些横切问题,例如日志记录或基准测试。那么

[18:44] 它实际上是如何运作的呢?好吧,你已经看到它

[18:47] 在这里发生了。如果我们向下滚动我们有帐户

[18:50] 素数函数,除了带有日志记录和装饰器基准的装饰器之外,我们实际上不需要

[18:54] 向该函数添加任何其他内容

[18:57] 。所以基本上,我们不必

[19:01] 在任何地方复制粘贴基准测试或记录代码,我们可以

[19:04] 简单地装饰单行函数。这

[19:07] 已经使它的工作量减少了很多,可能会出现一个问题,

[19:11] 有时您可能想将参数传递给这些

[19:14] 装饰器。例如,对于日志记录,我们可能希望

[19:17] 为其提供一个日志记录对象。或者,如果您想更好地

[19:21] 控制基准测试的发生方式,我们可能希望

[19:24] 为其提供一些设置。作为示例,我将向您展示如何使用

[19:27] 日志记录来执行此操作。让我们看看

[19:31] 它是如何工作的。嗯,原则上。这只是一个

[19:34] 函数调用。所以我们能做的,不是让这个

[19:38] 函数获取一个函数并返回另一个函数,而是

[19:41] 可以在它上面添加另一个层。所以我要做的

[19:46] 是在牙医中,然后将此函数重命名为装饰器,

[19:51] 因为它原则上是一个装饰器,然后我们将

[19:54] 创建另一个通过日志记录调用的函数。所以

[19:58] 现在我要做的唯一一件事就是

[20:00] 简单地在原始的

[20:04] 日志函数周围添加另一个函数层,然后

[20:07] 使用日志记录调用它上面的层。这是一个获取

[20:11] 参数的常规函数​​,例如,它获取一个记录器对象。

[20:16] 然后这将返回装饰器函数。所以我们

[20:20] 需要在这里添加另一行,返回

[20:24] 装饰器。现在我们不再编写记录点信息,而是

[20:28] 编写记录器点信息。

[20:31] 那么这里使用logger对象,我们该如何使用呢?好吧,假设

[20:35] 在文件顶部,我们将定义一个记录器,

[20:39] 这是记录点获取记录器。

[20:43] 我们称其为我的应用程序。顺便说一句,拥有一个记录器对象

[20:45] 有时很有用,例如,如果您需要添加

[20:48] 特定的处理程序,您可以使用基本的日志记录设置来完成此操作。

[20:53] 这样我们就有了记录器对象。现在,当我们实际调用

[20:57] withlogging 装饰器时,

[21:00] 我们只需将其传递给记录器并丢弃,我实际上可以删除,

[21:04] 因为我们不再需要它了。我们看到它的

[21:07] 工作方式完全相同,只是现在它使用记录器

[21:10] 对象,并且我们可以更改该对象中的内容。因此,这是

[21:13] 向装饰器提供一些参数的好方法,而不必

[21:16] 为每个需要它的函数运行一堆代码

[21:20] ,没有稍微不同的方法,您也可以

[21:23] 这样做。在这种情况下,如果我们想添加日志记录,我们总是需要

[21:27] 提供记录器对象,这可能不是我们

[21:30] 真正想要做的。您可以做的是使用

[21:33] func 工具部分函数应用程序来创建一个

[21:37] 已经应用了 logger 参数的装饰器。 那看起来 怎么样

[21:41] ? 好吧,我们可以创建一个变量,让我们

[21:44] 用默认日志记录来调用它。

[21:49] 这是 func 工具, 它是带有日志记录功能

[21:53] 的部分应用程序 。 我们将

[21:57] 像这样将它传递给情人。因此,partial 的作用是

[22:02] 接受一个函数并返回一个 已应用 一些参数的新函数 。因此,

[22:06] 当您调用它时, 您不必再传递这些

[22:09] 参数。 因此,默认日志记录

[22:12] 现在是一个不获取记录器对象的函数,因为您

[22:16] 已经在此处应用了它。现在,我们不必编写此内容,只需

[22:20] 使用默认日志记录即可编写,如下所示。尽管

[22:25] IDE 不再理解这是

[22:27] 一个装饰器,但它仍然是一个装饰器,尽管颜色

[22:30] 看起来不对。但现在当你运行这个时,你会发现实际上存在

[22:33] 一个问题,因为这是一个函数而不是函数

[22:37] 调用。所以这是一种方法。然后基本上

[22:41] 又起作用了。但这当然不太好。因此,

[22:43] 为了使其正常工作,我们还需要修复一件事,因为

[22:46] 默认日志记录现在是一个不带任何

[22:49] 参数的函数。这就是装饰器的工作,对吧,

[22:52] 因为装饰器需要看起来像这样。因此,因为我们

[22:55] 在这里使用了部分,所以我们实际上可以通过

[22:59] 删除该层然后将其添加回此处来再次简化它。现在我

[23:04] 可以做到这一点。我们也称其为日志记录。

[23:10] 然后我们只需要返回包装器,就像这样。所以

[23:13] 现在这里发生的事情是,日志记录又是一个简单的

[23:16] 装饰器函数,我们传递或不传递参数。由于

[23:21] 我们的部分函数应用,这就是它

[23:24] 发生的地方。现在我可以简单地使用默认登录来编写,就像

[23:29] 这样。当我运行这段代码时,我们再次得到相同的

[23:32] 结果。例如,您现在可以做的是移动

[23:36] 与日志记录相关的所有内容。因此,使用默认日志记录将

[23:40] 记录器对象记录到单独的文件中,只需导入

[23:44] 默认日志记录装饰器,然后在任何地方使用它。然后

[23:48] 您不必关心该记录器对象,因为

[23:50] 它已在其他地方处理,您只需使用默认

[23:53] 日志记录即可编写。现在仍然存在耦合,因为每当我们想要添加日志记录时,我们都会依赖

[23:56] 默认日志记录,但它已经

[24:00] 简单得多,至少以这种方式添加日志记录到函数中。

[24:04] 因此,装饰器是解决这些横切

[24:07] 问题的非常酷的解决方案,因为它们允许您

[24:09] 以这种方式轻松添加基准测试或日志记录。 那么您是否应该 在所有您可能想要这样做的地方

[24:13] 使用它们呢 ? 嗯,他们也有

[24:15] 一些问题我认为你需要注意

[24:18] ,特别是两个问题。首先,

[24:21] 如果您定义了装饰器,您的代码

[24:24] 实际上可能会变得更难以阅读。您可能会想,这怎么可能,

[24:27] 因为装饰器确实简化了事情?好吧,问题

[24:31] 是,如果您使用像这里这样的装饰器,例如

[24:33] ,使用默认日志记录,我们不知道作为用户

[24:37] 这个装饰器,它实际上做了什么以及它如何包装

[24:41] 原始函数。与基准相同。因此,如果

[24:45] 您使用大量装饰器和大量不同的组合,

[24:49] 可能很难理解发生了什么。例如

[24:52] ,我们在进行基准测试之前进行记录还是 在记录之后

[24:56] 进行基准测试,否则 我们可能不知道,除非我们

[25:00] 深入了解装饰器的工作原理以及它们如何

[25:03] 与 Python 中的常规函数​​调用相结合。因此,要小心

[25:07] 添加太多的装饰器层来运行,因为

[25:10] 这会影响可读性。 装饰器的

[25:13] 第二个潜在 问题是它们可能会修改 函数的 签名 。

[25:17] 在本例中,我们不这样做,我们

[25:20] 只是调用函数并返回值。所以不

[25:24] 改变这一点。但原则上,您可以编写一个装饰器

[25:28] ,该装饰器返回不同类型的值,或者具有一个

[25:32] 包装函数,该函数具有

[25:36] 与其包装的函数不同的参数。这可能会变得

[25:39] 非常令人困惑,我不久前制作了一个关于 Hydra

[25:43] 包的视频,我在顶部放置了一个实际执行此操作的链接。所以

[25:47] Hydra是一个配置读取库。这对于装饰器来说很有效

[25:51] ,但它所做的是你提供一个 main 函数,

[25:54] 然后在它上面写出 Hydra 。但这实际上

[25:57] 向主函数添加了一个配置对象,但您仍然必须

[26:01] 在其他地方调用不带参数的主函数。所以

[26:04] 这真的破坏了类型系统,这是一种非常糟糕的

[26:08] 设计实践。因此,如果您定义装饰器,我的

[26:11] 建议是不要更改环绕的函数签名,

[26:15] 因为这会导致类型

[26:18] 检查问题。再次,这会导致可读性降低。总的来说,我

[26:21] 认为装饰器也很好,特别是对于

[26:24] 日志记录和基准测试等 低级 事物。对于

[26:28] 更高级别的事物,我自己倾向于不使用装饰器

[26:32] 。我注意到,在很多情况下,我只是依赖于

[26:36] 以逻辑方式组合函数和对象。对我来说,

[26:40] 该解决方案在 99% 的情况下都非常有效,

[26:44] 尤其是如果您有更复杂的软件设计,它

[26:47] 实际上可以帮助单独依赖更基本的功能,以

[26:51] 确保语言

[26:55] 不会使您的代码更难阅读。让事情变得简单。

[26:59] 坚持更基本的编程语言功能,

[27:03] 实际上可以使代码更易于阅读和维护,

[27:06] 尤其是如果您不久前编写了

[27:09] 装饰器代码,然后很多软件开发人员或实习生都必须

[27:13] 看一下它。他们还需要了解

[27:16] 装饰器是什么,否则,他们将很难

[27:18] 理解代码的作用。因此,最后,如果您只

[27:21] 在真正需要时使用这些更高级的功能, 那么

[27:24] 将来 就可以节省您向其他人解释这一点的工作

[27:28] 。 但我实际上很想听听你

[27:31] 对此的想法。您自己在

[27:34] 代码中使用过装饰器吗?您以什么方式使用它们以及您为

[27:38] 每个人提供的提示,只需将它们发布在下面的评论中即可。我

[27:41] 在这个视频中使用了一些功能特性。实际上。 frontals 是一个

[27:44] 非常酷的 Python 包。如果您还没有使用它。你

[27:47] 绝对应该尝试一下。您可能想观看

[27:50] 接下来的视频,我在其中更详细地讨论了 frontals

[27:53] 包,并向您展示了一些可以用它做的很酷的事情。

[27:56] 感谢您的观看并保重

⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.