横切关注点:日志与认证的痛点
45s直击开发者日常痛点,引发共鸣,吸引观众继续观看解决方案。
▶ Play ClipThis 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.
Logging, authentication, and benchmarking are hard to integrate without code duplication or coupling.
Uses abstract component and decorator classes; decorators wrap components and can be stacked.
Python's first-class functions allow simpler decorators: a function that takes a function and returns a wrapped function.
Using @decorator above a function definition is syntactic sugar for applying the decorator.
Use functools.wraps to copy the original function's name and docstring to the wrapper.
Add an outer function that takes arguments and returns the actual decorator; or use functools.partial.
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.
"Title is accurate; video thoroughly covers Python decorators from basics to advanced usage."
What are cross-cutting concerns?
Features like logging, authentication, and benchmarking that affect multiple parts of a codebase.
What is the classic decorator design pattern?
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?
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?
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?
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?
It can reduce code readability because the order and effect of decorators may be unclear.
24:15
Why should decorators avoid changing function signatures?
Changing signatures breaks type checking and reduces code clarity.
25:13
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:58Preserving metadata with functools.wraps
Important practical tip for correct decorator behavior.
17:55Readability trade-off
Warns against overusing decorators, a key design principle.
24:15[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.