[0:00] 如果您已经开发软件一段时间了,您会 [0:02] 注意到某些类型的功能 [0:04] 确实很难很好地集成到您的代码中,而不 [0:08] 导致大量重复或大量额外耦合,例如 [0:11] 日志记录。所以你有一个函数,你想要记录一些东西。 [0:15] 因此,只需添加一堆记录所有内容的行即可。您是否 [0:19] 在代码中的任何地方都这样做,从而导致大量额外的 [0:21] 代码行?如果您有一个记录器对象,您会导入它吗?然后 [0:25] 到处使用它导致耦合?或者您是否 [0:28] 为每个需要记录的函数(即 [0:31] 记录器对象)添加一个额外的参数,这样您就可以在任何地方获得额外的参数。另一个 [0:35] 例子是身份验证。因此,您的代码中可能有一些端点 [0:39] ,您希望确保实际上 [0:42] 允许用户调用这些端点。 那么,您是否只需 [0:46] 为每个需要它的功能 复制粘贴 [0:49] 该身份验证代码? 然后你有很多代码重复?或者将其放入 [0:52] 一个单独的函数中,然后必须 [0:56] 在各处导入和调用,这会增加耦合和重复。所以 [0:59] 这些东西也被称为横切关注点,因为 [1:03] 它们基本上遍布你的代码。 除了登录身份验证之外, 还有几个 [1:06] 这样的例子,例如 [1:08] 通过事件进行基准测试或用户行为跟踪,基本上, [1:12] 这些事情是课堂上的巨大痛苦。 [1:15] 那么如何解决呢?好吧,一个可能的解决方案是使用 [1:19] 装饰器执行经典的面向对象装饰器设计 [1:23] 模式,但在 Python 中,您实际上不应该使用它, [1:26] 因为它可以更优雅地解决问题。所以今天, [1:30] 我将向您展示如何在 Python 中做到这一点。我还 [1:33] 对是否应该使用装饰器有一些想法, [1:36] 因为,有两个问题需要注意 [1:40] 。现在,在我们开始之前,我有一些东西要给你。如果您想 [1:44] 更好地分析代码并检测 [1:48] 需要解决的问题,我创建了一个免费的 [1:51] 代码诊断研讨会,您可以通过访问 [1:54] arjan.codes/diagnosis 加入该研讨会。它是完全免费的。这是一个研讨会 [1:59] ,解释了一个三因素框架,以帮助您 [2:02] 更好地决定应该在哪里以及如何改进 [2:05] 代码。为了说明如何做到这一点,我看一下一些 [2:09] 现有的软件包,您 [2:13] 目前可能在自己的项目中实际使用这些软件包。所以 arjan.codes/diagnosis [2:15] 加入免费研讨会。现在,让我们深入了解 [2:19] 装饰器。经典装饰器设计模式的想法 [2:23] 是,您基本上采用一个对象,并且想要 [2:26] 向其添加一些行为,我将向您展示一个简单的示例,说明 [2:30] 其工作原理。所以我这里有一个函数是 prime ,它基本上 [2:34] 检测一个数字是否是素数,该算法非常 [2:38] 基本。如果您有,特别是如果您有更大的素数, [2:41] 那么这不是您想要使用的算法类型 [2:43] 。还有其他更好的算法,我不会 [2:46] 在本视频中提及。但基本上,它检查一个数字是否 [2:49] 是素数,我将使用这个函数来向您展示 [2:51] 如何装饰一个实际有效的模式。现在 [2:54] 装饰器模式有两个重要的部分。有一个 [2:57] 组件,它基本上是具有某种 [3:00] 行为的类。然后还有一些装饰器对象,它们可以 [3:04] 向该组件添加行为,也可以向其他 [3:08] 装饰器添加行为。因此,我在这个示例中设置了一个非常基本的类结构, [3:12] 以便快速向您展示该模式是如何工作的。 [3:15] 有一个抽象组件类,它将成为 [3:18] 其他所有内容的超类。它有一个执行方法, [3:21] 它返回一个整数并获取一个上限。 [3:24] 我将在这个执行方法中做的 事情 [3:27] ,这就是我们 在具体组件中的位置,它将确定 [3:31] 范围内直到上限的每个数字是否 [3:34] 是素数。所以这当然是素数函数, [3:38] 然后只计算这些数字。这就是它 [3:41] 返回的内容。结果,这就是执行的作用。再说一遍,这个 [3:45] 这不是你在 Python 中设置它的方式,我只是想向 [3:47] 你展示装饰器模式工作的基本方式。这 [3:51] 就是我们的组成部分。这就是它的行为。 装饰器 模式 [3:54] 所做的是你没有的,这就是 [3:57] 我这里所拥有的,一个抽象装饰器,它完成了 [4:00] 抽象组件的子类。这就是这堂课。这 [4:04] 添加了一个初始化程序,它本身作为参数,一个 [4:08] 抽象组件,并将其存储在一个修饰的 [4:13] 实例变量中。所以装饰器引用了许多 [4:17] 组件。由于装饰器本身也是一个 [4:20] 组件,因此该引用也可以是另一个装饰器。这样 [4:23] 你就可以添加一系列的东西了。我 [4:27] 创建了一个装饰器示例,它是一个基准 [4:31] 装饰器。所以当然需要有 [4:35] 上限的执行方法返回整数,但是我要做的是使用 [4:39] 出生计数器函数来记录开始时间。然后,我 [4:43] 通过在我们有引用的装饰对象上调用执行来计算该值 [4:48] ,我记录酶,确定 [4:52] 运行时是什么,然后记录一些 [4:55] 有关该信息的信息,然后返回该值。所以它 [4:58] 基本上所做的就是它包装了 [5:00] 来自自点装饰实例变量的原始执行方法 [5:04] 。这就是基准装饰器。然后 [5:08] 运行此代码的方法就是这样,我 [5:11] 在这里初始化了一些日志记录,但随后我创建了该组件。这就是 [5:15] 这里的组件,它具有 [5:18] 确定有多少个素数 的执行方法 [5:21] ,然后我 创建一个基准装饰器,它将组件作为 [5:26] 参数。最后,我执行基准 [5:30] 装饰器,我可能不应该调用这个组件, [5:32] 因为这有点令人困惑。因此,让我们将其重命名为 [5:36] 基准装饰器,如下所示。然后在这里,我还将 [5:42] 调用基准装饰器上的执行。所以这里发生的事情 [5:45] 是我用数字 100k 调用执行,在这种情况下,所以 [5:51] 这将运行基准装饰器点执行步骤, [5:54] 因为开始时间依次调用自学 [5:58] 装饰器,执行,它会执行此操作,然后它返回 [6:01] ,并锁定最后的信息。所以当 [6:05] 我运行这个时,这就是我们得到的,我们得到日志信息。 [6:09] 显然,这花了 0.08 秒。我们还可以存储一个 [6:13] 值。 [6:16] 然后我们也打印该值。 [6:19] 现在我们看到 9592 个素数低于 100,000。 [6:25] 装饰器设计模式的有趣之处在于, [6:27] 您现在可以向这些额外的装饰器添加额外的层,例如 [6:31] ,假设我想创建一个名为 [6:34] 日志装饰器的类,我想做更多的日志记录,我们总是想做 [6:38] 更多的日志记录。这也是一个抽象装饰器。它 [6:42] 会有一个执行方法,就像其他 [6:47] 装饰器一样,对吗?所以这段代码并不是我们想要的。但我们 [6:51] 想做的更简单,我们要 [6:55] 在这里打印一些日志信息,比如调用,假设我们要调用 [6:59] self 点,装饰它。 [7:02] 类名。这就是我们想要打印的内容,就像这样。 [7:09] 我将复制该行。 [7:15] 就像这样。现在我要写 value equals self dotdecorated [7:22] dotexecute。我们将传递它的上限 [7:26] ,就像这样。当然,最后我们会返回该值。 [7:31] 现在我们能做的就是回到主函数,我们 [7:35] 可以将日志记录添加到组件和装饰器的序列中。因此, [7:38] 如果我现在添加一个日志装饰器,它是一个 [7:44] 获取组件的日志装饰器对象,就像这样。 [7:49] 我没有将组件传递给基准装饰器,而是将 日志 [7:52] 记录装饰器传递给基准装饰器。现在我们 [7:57] 有了一个由日志记录装饰器包裹的组件, [8:00] 该组件又由基准装饰器包裹。因此,当我 [8:04] 运行此命令时,您会看到我们现在 [8:07] 在这里获得额外的日志记录信息。您还可以看到这是由我们的日志记录 [8:12] 组件生成的。在这里我们看到基准装饰器是愚蠢的 [8:17] 调用登录装饰器的执行。 这就是 我们得到的 [8:21] 序列 。 显然,由于 [8:23] 我们正在执行额外的类结构和登录,因此需要更长的时间, [8:26] 即 0.09 秒。而且切换也相对容易, [8:30] 因为也许您不想登录成为 [8:33] 基准测试的一部分,对吧。所以在这种情况下,我们要做的 [8:36] 是,复制这一行并将 [8:40] 其移到此处,然后将其删除。现在我要做 [8:45] 的是基准装饰器将获取组件 [8:49] 。日志装饰器将获得 [8:54] 基准装饰器。 所以我们实际上并没有测量 [8:57] 登录某项内容需要 多 长时间。 [9:00] 但我们实际上是 在测量确定某物是否为 [9:02] 素数 的执行时间 。 然后我们所做的就是调用记录装饰器 [9:07] 点执行,然后当我们运行这个数字时,我们 [9:11] 仍然得到 0.09 秒,这可能与 CPU 正在执行的其他任务 [9:15] 有关 。 或者可能还有其他一些 [9:18] 方面导致速度变慢,例如 [9:21] 调用堆栈中的额外深度,诸如此类。但现在记录是以 [9:24] 不同的顺序完成的。首先,我们编写日志,然后调用 [9:26] 基准装饰器,然后执行具体组件的方法 [9:30] 。所以有点不同。 [9:33] 装饰器模式的有趣之处在于,您基本上可以 按照 [9:35] 您喜欢的任何方式确定顺序。它 [9:39] 也会影响应用程序的行为。 [9:42] 现在我设置的方式实际上纯粹是遵循 [9:45] 装饰器设计模式的经典版本,该模式基本上是 [9:49] 为不支持 [9:53] 仅接受类的函数的语言而设计的,然后这就是我们必须这样做的方式, [9:55] 因为Python对函数有很好的支持, [9: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] 感谢您的观看并保重