Python的基础装饰器使用指南

1. 概述

1.1 什么是 Python 装饰器

先说一个结论:

装饰器是一个以函数为参数的函数

装饰器也可以叫函数包装器,是一个以函数为参数的函数。它的功能就是将一个作为参数传入包装器的函数进行包装,为其添加更多的功能同时不需要了解被包装函数的内部实现。

【引例】:
有一个没有被包装的函数,看起来是这样的

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">def</span> <span style="color:#f08d49">a_function</span><span style="color:#cccccc">(</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">"-劳资是普通函数函数-"</span><span style="color:#cccccc">)</span>
    
a_function<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>        <span style="color:#999999"># 调用执行这个函数</span>
</code></span></span>

Out[i]

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-bash"><span style="color:#7ec699">'-劳资是普通函数函数-'</span>
</code></span></span>

现在李华写好了一个包装器a_decorator给你,你不需要关心它的实现,只需要使用它来装饰上面的函数。那么你就而已这样写,然后并运行它:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@a_decorator</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">a_function</span><span style="color:#cccccc">(</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">"-劳资是被装饰函数-"</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[i]:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">在被装饰函数前面做点事
<span style="color:#67cdcc">-</span>劳资是被装饰函数<span style="color:#67cdcc">-</span>
在被装饰函数后面做点事
</code></span></span>

从另外一个角度理解,包装器是一种函数的集成机制,与类的继承机制有很多相似之处却也有不同。在面向对象编程类的继承中:

  • 子类继承父类,子类接受父类作为参数,子类即获得父类的方法和属性。同时子类必须在父类的基础熵实现一些其它的功能使得子类在某个方面比父类更强大以至于子类能够完成更多具体的工作
  • 包装器从被包装函数那继承它的功能,同时包装器又需要在被包装函数功能的基础上实现更多的功能,使得包装器更加强大以至于能够实现更多功能

但是与类的继承机制又有区别:

  • 在类的继承中一般我们是先实现父类后实现子类,而在包装器的用法中则正好相反,是先实现包装器后实现被包装的函数。因此说类继承是由父到子,而函数装饰是子到父

1.2 使用 Python 装饰器的好处

装饰器在 Python 中被广泛应用于函数和类的修饰和扩展。它的作用和优势包括:

  • 扩展函数功能:装饰器可以在不修改函数代码的情况下,为函数添加额外的功能,例如日志记录、性能统计、输入验证等。
  • 代码复用和简化:装饰器可以将一些通用的功能封装成装饰器函数,然后通过修饰器的方式应用到多个函数中,避免重复编写相同的代码。
  • 解耦和增强可读性:装饰器可以将函数的核心逻辑和辅助功能分离,使代码更具可读性和可维护性。
  • 不改变原有代码结构:通过装饰器修饰函数,不会修改原有函数的代码结构,同时允许我们在运行时动态地修改函数行为。

2. 装饰器初探

2.1 装饰器的基本语法

这一节我们仅仅简单地看一下装饰器最简单的语法,为下一章体验实际框架中的装饰器做必要的铺垫。装饰器的基本语法是使用 @ 符号将装饰器函数应用于目标函数。装饰器函数接受目标函数作为参数,并在函数内部对目标函数进行修改或扩展。

下面是一个简单的装饰器示例:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-python"><span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator_func</span><span style="color:#cccccc">(</span>func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">"函数执行前"</span><span style="color:#cccccc">)</span>
        func<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">"函数执行后"</span><span style="color:#cccccc">)</span>
    <span style="color:#cc99cd">return</span> wrapper

<span style="color:#cccccc">@decorator_func</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">hello</span><span style="color:#cccccc">(</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">"Hello, world!"</span><span style="color:#cccccc">)</span>

hello<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
</code></span></span>

在上述示例中,我们定义了一个装饰器函数 decorator_func,它接受一个函数作为参数,并定义了一个内部函数 wrapper。在 wrapper 函数内部,我们添加了额外的功能,并在函数执行前后打印一些信息。

通过在 hello 函数定义前使用 @decorator_func,我们将 hello 函数应用了装饰器。当调用 hello 函数时,实际上是调用了经过装饰器修饰后的 wrapper 函数,从而实现了在函数执行前后添加额外功能的目的。

2.2 装饰器的应用场景

装饰器在实际开发中有多种应用场景,以下是一些常见的应用场景:

  • 记录日志:通过装饰器可以方便地记录函数的调用信息、参数和返回值,用于调试和日志记录。
  • 计时统计:装饰器可以用于测量函数的执行时间,用于性能分析和优化。
  • 输入验证:通过装饰器可以对函数的输入参数进行验证,确保参数的合法性和有效性。
  • 缓存结果:装饰器可以用于缓存函数的计算结果,提高函数的执行效率。
  • 授权和身份验证:装饰器可以用于对函数进行授权和身份验证,限制函数的访问权限。

以上只是一些装饰器的应用场景示例,实际上,我们可以根据具体需求自定义和组合装饰器,以实现更复杂的功能和逻辑。

3. 体验:Django 框架中的装饰器

3.1关于本节

Django 是 Python 语言生态中最重要的 重量级 Web 框架 没有之一。 在 Django 框架中提供了一些内置的装饰器,用于实现不同的功能和需求。下面将带大家一起体验一下 Django 中常用的装饰器及其用法。本节目的并不需要你已经全面掌握装饰器,而是让似懂非懂的你,从几个简单的例子中对装饰器有更多的感性认识。

3.2 @login_required 装饰器

@login_required 装饰器用于限制只有已登录用户才能访问某个视图函数。如果未登录用户访问被装饰的视图函数,将会被重定向到登录页面。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>contrib<span style="color:#cccccc">.</span>auth<span style="color:#cccccc">.</span>decorators <span style="color:#cc99cd">import</span> login_required
<span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>shortcuts <span style="color:#cc99cd">import</span> render

<span style="color:#cccccc">@login_required</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">my_view</span><span style="color:#cccccc">(</span>request<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#999999"># 仅允许已登录用户访问的视图函数</span>
    <span style="color:#cc99cd">return</span> render<span style="color:#cccccc">(</span>request<span style="color:#cccccc">,</span> <span style="color:#7ec699">'my_view.html'</span><span style="color:#cccccc">)</span>
</code></span></span>

在上述示例中,我们通过在视图函数定义前添加 @login_required 装饰器,限制了只有已登录的用户才能访问 my_view 视图函数。如果未登录用户尝试访问该视图函数,Django 会将其重定向到登录页面。

3.3 @permission_required 装饰器

@permission_required 装饰器用于限制只有拥有指定权限的用户才能访问某个视图函数。如果用户没有相应权限,将会显示一个错误页面。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>contrib<span style="color:#cccccc">.</span>auth<span style="color:#cccccc">.</span>decorators <span style="color:#cc99cd">import</span> permission_required
<span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>shortcuts <span style="color:#cc99cd">import</span> render

<span style="color:#cccccc">@permission_required</span><span style="color:#cccccc">(</span><span style="color:#7ec699">'polls.can_vote'</span><span style="color:#cccccc">)</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">my_view</span><span style="color:#cccccc">(</span>request<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#999999"># 仅允许拥有 'polls.can_vote' 权限的用户访问的视图函数</span>
    <span style="color:#cc99cd">return</span> render<span style="color:#cccccc">(</span>request<span style="color:#cccccc">,</span> <span style="color:#7ec699">'my_view.html'</span><span style="color:#cccccc">)</span>
</code></span></span>

在上述示例中,我们通过在视图函数定义前添加 @permission_required('polls.can_vote') 装饰器,限制了只有拥有 'polls.can_vote' 权限的用户才能访问 my_view 视图函数。如果用户没有相应的权限,将会显示一个错误页面。

3.4 @csrf_exempt 装饰器

@csrf_exempt 装饰器用于在视图函数中关闭 跨站请求伪造(CSRF) 保护。通过使用该装饰器,可以允许未经 CSRF 保护的 POST 请求访问视图函数。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>views<span style="color:#cccccc">.</span>decorators<span style="color:#cccccc">.</span>csrf <span style="color:#cc99cd">import</span> csrf_exempt
<span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>http <span style="color:#cc99cd">import</span> HttpResponse

<span style="color:#cccccc">@csrf_exempt</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">my_view</span><span style="color:#cccccc">(</span>request<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#999999"># 允许未经 CSRF 保护的 POST 请求访问的视图函数</span>
    <span style="color:#cc99cd">if</span> request<span style="color:#cccccc">.</span>method <span style="color:#67cdcc">==</span> <span style="color:#7ec699">'POST'</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">return</span> HttpResponse<span style="color:#cccccc">(</span><span style="color:#7ec699">'POST request'</span><span style="color:#cccccc">)</span>
    <span style="color:#cc99cd">else</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">return</span> HttpResponse<span style="color:#cccccc">(</span><span style="color:#7ec699">'GET request'</span><span style="color:#cccccc">)</span>
</code></span></span>

在上述示例中,我们通过在视图函数定义前添加 @csrf_exempt 装饰器,允许未经 CSRF 保护的 POST 请求访问 my_view 视图函数。在该视图函数中,我们根据请求的方法返回不同的响应。

3.5 @cache_page 装饰器

@cache_page 装饰器用于缓存整个视图函数的输出结果,从而提高页面的响应速度。可以指定缓存的时间,以控制缓存的有效期。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>views<span style="color:#cccccc">.</span>decorators<span style="color:#cccccc">.</span>cache <span style="color:#cc99cd">import</span> cache_page
<span style="color:#cc99cd">from</span> django<span style="color:#cccccc">.</span>shortcuts <span style="color:#cc99cd">import</span> render

<span style="color:#cccccc">@cache_page</span><span style="color:#cccccc">(</span><span style="color:#f08d49">60</span> <span style="color:#67cdcc">*</span> <span style="color:#f08d49">10</span><span style="color:#cccccc">)</span>  <span style="color:#999999"># 缓存 10 分钟</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">my_view</span><span style="color:#cccccc">(</span>request<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#999999"># 缓存整个视图函数的输出结果</span>
    <span style="color:#cc99cd">return</span> render<span style="color:#cccccc">(</span>request<span style="color:#cccccc">,</span> <span style="color:#7ec699">'my_view.html'</span><span style="color:#cccccc">)</span>
</code></span></span>

在上述示例中,我们通过在视图函数定义前添加 @cache_page(60 * 10) 装饰器,将 my_view 视图函数的输出结果缓存起来,有效期为 10 分钟。在该视图函数中,我们返回渲染后的模板。

4. 函数装饰器

4.1 装饰器原理与基本语法

前面的章节我们简单地了解和使用了装饰器,从本节开始,我们全面讲解它。装饰器是一种Python语法特性,它允许我们在不修改原始函数代码的情况下,通过添加额外的功能来扩展函数的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数或可调用对象。

装饰器的语法结构如下所示:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><a><code class="language-py"><span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator_func</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#999999"># 在调用原始函数之前执行的代码</span>
        result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#999999"># 在调用原始函数之后执行的代码</span>
        <span style="color:#cc99cd">return</span> result
    <span style="color:#cc99cd">return</span> wrapper
</code></a></span></span>

上述代码中,我们定义了一个名为 decorator_func 的装饰器函数,它接受一个 original_func 参数,代表被装饰的原始函数。在装饰器内部,我们定义了一个名为 wrapper 的包装函数,它负责在调用原始函数之前和之后执行额外的代码逻辑。

装饰器函数内部的 wrapper 函数使用 *args 和 **kwargs 来接受任意数量的位置参数和关键字参数,并将它们传递给原始函数。这样,我们可以在不影响原始函数签名的情况下,适应不同参数的函数。

要使用装饰器我们需要将其应用于目标函数,可以使用 @ 符号将装饰器放置在函数定义之前,例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><a><code class="language-py"><span style="color:#cccccc">@decorator_func</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">my_function</span><span style="color:#cccccc">(</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#999999"># 函数的代码逻辑</span>
</code></a></span></span>

通过这样的语法,我们将 decorator_func 装饰器应用于 my_function 函数。这也就相当于:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><a><code class="language-py">my_function <span style="color:#67cdcc">=</span> decorator_func<span style="color:#cccccc">(</span>my_function<span style="color:#cccccc">)</span>
</code></a></span></span>

现在,每当我们调用 my_function 时,实际上是调用装饰器返回的 wrapper 函数。在 wrapper 函数中,我们可以添加额外的功能、修改函数的行为,然后调用原始函数,并返回其结果。

4.2 应用装饰器修改函数行为

装饰器提供了一种灵活的方式来修改函数的行为。我们可以通过装饰器添加额外的功能,例如日志记录、输入验证、性能测试等。本章将介绍如何使用装饰器来修改函数的行为。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#999999"># 一个添加日志记录功能的装饰器</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">log_decorator</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"调用函数 </span><span style="color:#cccccc">{</span>original_func<span style="color:#cccccc">.</span>__name__<span style="color:#cccccc">}</span><span style="color:#7ec699">"</span><span style="color:#cccccc">)</span>
        result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"函数 </span><span style="color:#cccccc">{</span>original_func<span style="color:#cccccc">.</span>__name__<span style="color:#cccccc">}</span><span style="color:#7ec699"> 调用完毕"</span><span style="color:#cccccc">)</span>
        <span style="color:#cc99cd">return</span> result
    <span style="color:#cc99cd">return</span> wrapper
</code></span></span>

在上述示例中,我们定义了一个名为 log_decorator 的装饰器函数。它在调用原始函数之前打印一条日志,然后调用原始函数,最后再打印一条日志。现在,让我们使用该装饰器来装饰一个函数:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@log_decorator</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">add_numbers</span><span style="color:#cccccc">(</span>a<span style="color:#cccccc">,</span> b<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">return</span> a <span style="color:#67cdcc">+</span> b
</code></span></span>

当我们调用 add_numbers 函数时,装饰器会自动添加日志记录的功能:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">result <span style="color:#67cdcc">=</span> add_numbers<span style="color:#cccccc">(</span><span style="color:#f08d49">10</span><span style="color:#cccccc">,</span> <span style="color:#f08d49">5</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-javascript">调用函数 add_numbers
函数 add_numbers 调用完毕
</code></span></span>
<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span>result<span style="color:#cccccc">)</span>
</code></span></span>

Out[]: 15

通过装饰器,我们可以在不修改原始函数代码的情况下,增加额外的功能。

又如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">import</span> time

<span style="color:#cc99cd">def</span> <span style="color:#f08d49">performance_decorator</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        start_time <span style="color:#67cdcc">=</span> time<span style="color:#cccccc">.</span>time<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
        result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        end_time <span style="color:#67cdcc">=</span> time<span style="color:#cccccc">.</span>time<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
        execution_time <span style="color:#67cdcc">=</span> end_time <span style="color:#67cdcc">-</span> start_time
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"函数 </span><span style="color:#cccccc">{</span>original_func<span style="color:#cccccc">.</span>__name__<span style="color:#cccccc">}</span><span style="color:#7ec699"> 的执行时间为:</span><span style="color:#cccccc">{</span>execution_time<span style="color:#cccccc">}</span><span style="color:#7ec699">秒"</span><span style="color:#cccccc">)</span>
        <span style="color:#cc99cd">return</span> result
    <span style="color:#cc99cd">return</span> wrapper
</code></span></span>

在上述示例中,我们定义了一个名为 performance_decorator 的装饰器函数。它会在调用原始函数之前记录起始时间,在调用原始函数之后计算执行时间,并打印出来。现在,让我们使用该装饰器来装饰一个函数:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@performance_decorator</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">calculate_factorial</span><span style="color:#cccccc">(</span>n<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    factorial <span style="color:#67cdcc">=</span> <span style="color:#f08d49">1</span>
    <span style="color:#cc99cd">for</span> i <span style="color:#cc99cd">in</span> <span style="color:#cc99cd">range</span><span style="color:#cccccc">(</span><span style="color:#f08d49">1</span><span style="color:#cccccc">,</span> n<span style="color:#67cdcc">+</span><span style="color:#f08d49">1</span><span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        factorial <span style="color:#67cdcc">*=</span> i
    <span style="color:#cc99cd">return</span> factorial
</code></span></span>

当我们调用 calculate_factorial 函数时,装饰器会自动计算执行时间并输出:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">result <span style="color:#67cdcc">=</span> calculate_factorial<span style="color:#cccccc">(</span><span style="color:#f08d49">10</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]:函数 calculate_factorial 的执行时间为:6.4373016357421875e-06秒

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span>result<span style="color:#cccccc">)</span>
</code></span></span>

Out[]:3628800

通过这个例子我们可以看到,通过使用装饰器我们可以轻松地添加性能测试等额外的功能而无需修改原始函数的代码。

4.3 应用装饰器修改函数行为

带参数的装饰器是一种更加灵活和可定制的装饰器形式。它允许我们通过在装饰器函数上添加参数,来控制装饰器的行为。在本章中,我们将讨论如何编写带参数的装饰器,并提供一些示例来说明其用法。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">def</span> <span style="color:#f08d49">repeat</span><span style="color:#cccccc">(</span>num_times<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator_func</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
            <span style="color:#cc99cd">for</span> _ <span style="color:#cc99cd">in</span> <span style="color:#cc99cd">range</span><span style="color:#cccccc">(</span>num_times<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
                result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
            <span style="color:#cc99cd">return</span> result
        <span style="color:#cc99cd">return</span> wrapper
    <span style="color:#cc99cd">return</span> decorator_func
</code></span></span>

本例中我们定义了一个名为 repeat 的装饰器函数,它接受一个参数 num_times。装饰器函数内部定义了另一个函数 decorator_func,它才是真正的装饰器。decorator_func 内部的 wrapper 函数会重复调用原始函数 num_times 次。

我们可以使用带参数的装饰器来装饰一个函数如下:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@repeat</span><span style="color:#cccccc">(</span>num_times<span style="color:#67cdcc">=</span><span style="color:#f08d49">3</span><span style="color:#cccccc">)</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">greet</span><span style="color:#cccccc">(</span>name<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"Hello, </span><span style="color:#cccccc">{</span>name<span style="color:#cccccc">}</span><span style="color:#7ec699">!"</span><span style="color:#cccccc">)</span>

greet<span style="color:#cccccc">(</span><span style="color:#7ec699">"Alice"</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">Hello<span style="color:#cccccc">,</span> Alice!
Hello<span style="color:#cccccc">,</span> Alice!
Hello<span style="color:#cccccc">,</span> Alice!
</code></span></span>

通过在装饰器上添加参数 num_times=3,我们可以指定 greet 函数被调用的次数。这样,函数体内的打印语句将会重复执行三次。

又如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">import</span> time

<span style="color:#cc99cd">def</span> <span style="color:#f08d49">performance_decorator</span><span style="color:#cccccc">(</span>unit<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator_func</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
            start_time <span style="color:#67cdcc">=</span> time<span style="color:#cccccc">.</span>time<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
            result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
            end_time <span style="color:#67cdcc">=</span> time<span style="color:#cccccc">.</span>time<span style="color:#cccccc">(</span><span style="color:#cccccc">)</span>
            execution_time <span style="color:#67cdcc">=</span> end_time <span style="color:#67cdcc">-</span> start_time
            <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"函数 </span><span style="color:#cccccc">{</span>original_func<span style="color:#cccccc">.</span>__name__<span style="color:#cccccc">}</span><span style="color:#7ec699"> 的执行时间为:</span><span style="color:#cccccc">{</span>execution_time<span style="color:#cccccc">}</span><span style="color:#cccccc">{</span>unit<span style="color:#cccccc">}</span><span style="color:#7ec699">"</span><span style="color:#cccccc">)</span>
            <span style="color:#cc99cd">return</span> result
        <span style="color:#cc99cd">return</span> wrapper
    <span style="color:#cc99cd">return</span> decorator_func
</code></span></span>

本例中我们定义了一个名为 performance_decorator 的装饰器函数,它接受一个参数 unit,用于指定执行时间的单位。decorator_func 内部的 wrapper 函数会计算函数执行时间,并在打印时附上指定的单位。

我们可以这样使用:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@performance_decorator</span><span style="color:#cccccc">(</span>unit<span style="color:#67cdcc">=</span><span style="color:#7ec699">"秒"</span><span style="color:#cccccc">)</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">calculate_power</span><span style="color:#cccccc">(</span>base<span style="color:#cccccc">,</span> exponent<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    result <span style="color:#67cdcc">=</span> base <span style="color:#67cdcc">**</span> exponent
    time<span style="color:#cccccc">.</span>sleep<span style="color:#cccccc">(</span><span style="color:#f08d49">1</span><span style="color:#cccccc">)</span>  <span style="color:#999999"># 模拟函数执行的时间</span>
    <span style="color:#cc99cd">return</span> result
</code></span></span>

当我们调用 calculate_power 函数时,装饰器会自动计算执行时间并输出,单位为秒。

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">result <span style="color:#67cdcc">=</span> calculate_power<span style="color:#cccccc">(</span><span style="color:#f08d49">2</span><span style="color:#cccccc">,</span> <span style="color:#f08d49">10</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]:函数 calculate_power 的执行时间为:1.0002830028533936秒

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span>result<span style="color:#cccccc">)</span>  <span style="color:#999999"># 输出:1024</span>
</code></span></span>

5. 类装饰器

装饰器同样可以通过 Python 中的类(class)语法来实现。本章将介绍如何使用类装饰器,并解释类装饰器与函数装饰器的区别。

5.1 类装饰器的结构和用法

类装饰器是一种使用类来实现装饰器功能的方法,与函数装饰器不同的是 类装饰器 可以在实例化时执行额外的逻辑,并且可以跟踪状态。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">class</span> <span style="color:#f8c555">DecoratorClass</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">__init__</span><span style="color:#cccccc">(</span>self<span style="color:#cccccc">,</span> original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        self<span style="color:#cccccc">.</span>original_func <span style="color:#67cdcc">=</span> original_func

    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">__call__</span><span style="color:#cccccc">(</span>self<span style="color:#cccccc">,</span> <span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#999999"># 在调用原始函数之前执行的代码</span>
        result <span style="color:#67cdcc">=</span> self<span style="color:#cccccc">.</span>original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#999999"># 在调用原始函数之后执行的代码</span>
        <span style="color:#cc99cd">return</span> result
</code></span></span>

这个例子中,我们定义了一个名为 DecoratorClass 的类装饰器。它接受一个参数 original_func,代表被装饰的原始函数。类装饰器中的 __call__ 方法定义了装饰器实例被调用时的行为,类似于函数装饰器中的 wrapper 函数。

定义好类装饰器以后,我们可以这样来使用它:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@DecoratorClass</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">greet</span><span style="color:#cccccc">(</span>name<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"Hello, </span><span style="color:#cccccc">{</span>name<span style="color:#cccccc">}</span><span style="color:#7ec699">!"</span><span style="color:#cccccc">)</span>
</code></span></span>

通过将 DecoratorClass 应用为装饰器,我们将其实例化,并将被装饰的函数作为参数传递给构造函数。此时,类装饰器的 __call__ 方法会被调用。

5.2 带状态的类装饰器

在Python中,带状态的类装饰器是一种特殊类型的装饰器,它在装饰器实例中保持一些状态信息。这意味着每次调用被装饰的函数时,装饰器可以跟踪和更新状态。

通常情况下,函数装饰器是无状态的,它们只是在函数调用前后执行一些逻辑,而没有记住之前的状态。但是,有时我们希望装饰器能够保持某种状态,例如计数器、缓存等。这就是带状态的类装饰器的作用。

带状态的类装饰器通常是通过实现类的__init__() 和 __call__() 方法来实现的。在 __init__() 方法中,我们可以初始化状态变量,并在 __call__() 方法中执行装饰器的逻辑。

例如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">class</span> <span style="color:#f8c555">Counter</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">__init__</span><span style="color:#cccccc">(</span>self<span style="color:#cccccc">,</span> original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        self<span style="color:#cccccc">.</span>original_func <span style="color:#67cdcc">=</span> original_func
        self<span style="color:#cccccc">.</span>count <span style="color:#67cdcc">=</span> <span style="color:#f08d49">0</span>

    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">__call__</span><span style="color:#cccccc">(</span>self<span style="color:#cccccc">,</span> <span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        self<span style="color:#cccccc">.</span>count <span style="color:#67cdcc">+=</span> <span style="color:#f08d49">1</span>
        <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"函数 </span><span style="color:#cccccc">{</span>self<span style="color:#cccccc">.</span>original_func<span style="color:#cccccc">.</span>__name__<span style="color:#cccccc">}</span><span style="color:#7ec699"> 被调用了 </span><span style="color:#cccccc">{</span>self<span style="color:#cccccc">.</span>count<span style="color:#cccccc">}</span><span style="color:#7ec699"> 次"</span><span style="color:#cccccc">)</span>
        result <span style="color:#67cdcc">=</span> self<span style="color:#cccccc">.</span>original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#cc99cd">return</span> result
</code></span></span>

本例中,Counter 类装饰器在初始化时会将被装饰的原始函数保存在 self.original_func 中,并初始化计数器 self.count 为 0。在每次调用被装饰的函数时,__call__() 方法会递增计数器的值,并打印出函数被调用的次数。

我们可以这样来使用这个装饰器:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@Counter</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">add_numbers</span><span style="color:#cccccc">(</span>a<span style="color:#cccccc">,</span> b<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">return</span> a <span style="color:#67cdcc">+</span> b
</code></span></span>

有了这样的装饰,当我们调用 add_numbers 函数时,装饰器会自动记录函数被调用的次数,并输出计数信息。

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">result <span style="color:#67cdcc">=</span> add_numbers<span style="color:#cccccc">(</span><span style="color:#f08d49">10</span><span style="color:#cccccc">,</span> <span style="color:#f08d49">5</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]: 函数 add_numbers 被调用了 1 次

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span>result<span style="color:#cccccc">)</span>
</code></span></span>

Out[]:15

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">result <span style="color:#67cdcc">=</span> add_numbers<span style="color:#cccccc">(</span><span style="color:#f08d49">8</span><span style="color:#cccccc">,</span> <span style="color:#f08d49">2</span><span style="color:#cccccc">)</span>
</code></span></span>

Out[]:函数 add_numbers 被调用了 2 次

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span>result<span style="color:#cccccc">)</span>
</code></span></span>

Out[]:10

6. 装饰器的组合

然而,在实际开发中,我们可能需要同时应用多个装饰器,以实现更复杂的功能或修改函数的多个方面。本章将介绍如何组合多个装饰器,以及它们的执行顺序。一个组合装饰器的例子如下:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator1</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#999999"># 装饰器1的逻辑</span>
        result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#999999"># 装饰器1的逻辑</span>
        <span style="color:#cc99cd">return</span> result
    <span style="color:#cc99cd">return</span> wrapper

<span style="color:#cc99cd">def</span> <span style="color:#f08d49">decorator2</span><span style="color:#cccccc">(</span>original_func<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">def</span> <span style="color:#f08d49">wrapper</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
        <span style="color:#999999"># 装饰器2的逻辑</span>
        result <span style="color:#67cdcc">=</span> original_func<span style="color:#cccccc">(</span><span style="color:#67cdcc">*</span>args<span style="color:#cccccc">,</span> <span style="color:#67cdcc">**</span>kwargs<span style="color:#cccccc">)</span>
        <span style="color:#999999"># 装饰器2的逻辑</span>
        <span style="color:#cc99cd">return</span> result
    <span style="color:#cc99cd">return</span> wrapper
</code></span></span>

现在我们组合这两个装饰器,并将它们应用到一个函数上:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py"><span style="color:#cccccc">@decorator1</span>
<span style="color:#cccccc">@decorator2</span>
<span style="color:#cc99cd">def</span> <span style="color:#f08d49">greet</span><span style="color:#cccccc">(</span>name<span style="color:#cccccc">)</span><span style="color:#cccccc">:</span>
    <span style="color:#cc99cd">print</span><span style="color:#cccccc">(</span><span style="color:#7ec699">f"Hello, </span><span style="color:#cccccc">{</span>name<span style="color:#cccccc">}</span><span style="color:#7ec699">!"</span><span style="color:#cccccc">)</span>
</code></span></span>

本例中,greet 函数将先应用 装饰器decorator2,然后再应用 装饰器decorator1。这意味着 greet 函数的执行顺序将 按照装饰器的堆叠顺序 进行。

现在我们调用这个被多个装饰器装饰的 greet 函数:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-py">greet<span style="color:#cccccc">(</span><span style="color:#7ec699">"Jack"</span><span style="color:#cccccc">)</span>
</code></span></span>

其执行过程如:

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-javascript"># decorator1 的逻辑
# decorator2 的逻辑
Hello<span style="color:#cccccc">,</span> Jack<span style="color:#67cdcc">!</span>
# decorator2 的逻辑
# decorator1 的逻辑
</code></span></span>

也就是,首先 装饰器decorator2 的逻辑被执行,然后是 装饰器decorator1 的逻辑,最后是原始函数 greet 的逻辑。在函数执行完毕后,装饰器的逻辑又按照相反的顺序执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值