<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[麻薯ta爸的博客]]></title><description><![CDATA[一个记录学习、开发和生活灵感的个人博客]]></description><link>https://mashutaba.best</link><image><url>https://cdn.hashnode.com/uploads/logos/6a23b8dd8579a04d4fa8ba65/48d0d38b-0363-4a69-a6a2-37781fd84159.png</url><title>麻薯ta爸的博客</title><link>https://mashutaba.best</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 06 Jun 2026 11:23:16 GMT</lastBuildDate><atom:link href="https://mashutaba.best/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Python 装饰器：把函数包起来，但不把脑子绕进去]]></title><description><![CDATA[Python 里有一个很优雅、也很容易把初学者绕晕的东西：装饰器。
它看起来像这样：
@decorator
def hello():
    print("Hello")

很多人第一次看到 @xxx 会以为这是某种神秘语法。其实装饰器的本质很简单：

装饰器就是一个“接收函数，并返回新函数”的函数。

换句话说，它可以在不修改原函数代码的情况下，给原函数增加额外功能。
从一个普通函数开始
先看一]]></description><link>https://mashutaba.best/python</link><guid isPermaLink="true">https://mashutaba.best/python</guid><dc:creator><![CDATA[木木木]]></dc:creator><pubDate>Sat, 06 Jun 2026 08:17:07 GMT</pubDate><content:encoded><![CDATA[<p>Python 里有一个很优雅、也很容易把初学者绕晕的东西：装饰器。</p>
<p>它看起来像这样：</p>
<pre><code class="language-python">@decorator
def hello():
    print("Hello")
</code></pre>
<p>很多人第一次看到 <code>@xxx</code> 会以为这是某种神秘语法。其实装饰器的本质很简单：</p>
<blockquote>
<p>装饰器就是一个“接收函数，并返回新函数”的函数。</p>
</blockquote>
<p>换句话说，它可以在不修改原函数代码的情况下，给原函数增加额外功能。</p>
<h2>从一个普通函数开始</h2>
<p>先看一个普通函数：</p>
<pre><code class="language-python">def say_hello():
    print("Hello, Python!")

say_hello()
</code></pre>
<p>输出：</p>
<pre><code class="language-text">Hello, Python!
</code></pre>
<p>现在假设我们想在函数执行前后打印一些日志：</p>
<pre><code class="language-python">def say_hello():
    print("函数开始执行")
    print("Hello, Python!")
    print("函数执行结束")
</code></pre>
<p>这样当然可以，但问题是：如果有 10 个函数都要加日志呢？</p>
<p>一个个改，太累，也不优雅。</p>
<p>这时装饰器就派上用场了。</p>
<h2>函数也可以当作参数</h2>
<p>理解装饰器前，要先知道 Python 里的函数可以像普通变量一样传来传去：</p>
<pre><code class="language-python">def say_hello():
    print("Hello")

def run_func(func):
    func()

run_func(say_hello)
</code></pre>
<p>这里 <code>say_hello</code> 被当作参数传给了 <code>run_func</code>。</p>
<p>这就是装饰器成立的基础。</p>
<h2>写一个最简单的装饰器</h2>
<pre><code class="language-python">def log(func):
    def wrapper():
        print("函数开始执行")
        func()
        print("函数执行结束")
    return wrapper
</code></pre>
<p>这个 <code>log</code> 函数接收一个函数 <code>func</code>，然后在内部定义了一个新函数 <code>wrapper</code>。</p>
<p><code>wrapper</code> 做了三件事：</p>
<pre><code class="language-python">print("函数开始执行")
func()
print("函数执行结束")
</code></pre>
<p>最后，<code>log</code> 返回了这个 <code>wrapper</code>。</p>
<p>使用方式如下：</p>
<pre><code class="language-python">def say_hello():
    print("Hello, Python!")

say_hello = log(say_hello)

say_hello()
</code></pre>
<p>输出：</p>
<pre><code class="language-text">函数开始执行
Hello, Python!
函数执行结束
</code></pre>
<p>这就是装饰器的本质。</p>
<h2>@ 语法只是简写</h2>
<p>上面的代码：</p>
<pre><code class="language-python">say_hello = log(say_hello)
</code></pre>
<p>可以简写成：</p>
<pre><code class="language-python">@log
def say_hello():
    print("Hello, Python!")
</code></pre>
<p>完整代码：</p>
<pre><code class="language-python">def log(func):
    def wrapper():
        print("函数开始执行")
        func()
        print("函数执行结束")
    return wrapper


@log
def say_hello():
    print("Hello, Python!")


say_hello()
</code></pre>
<p>这两种写法完全等价。</p>
<p>所以：</p>
<pre><code class="language-python">@log
def say_hello():
    ...
</code></pre>
<p>本质上就是：</p>
<pre><code class="language-python">say_hello = log(say_hello)
</code></pre>
<h2>处理带参数的函数</h2>
<p>刚才的装饰器只能装饰没有参数的函数。</p>
<p>如果函数有参数：</p>
<pre><code class="language-python">def greet(name):
    print(f"Hello, {name}")
</code></pre>
<p>原来的装饰器就会报错。</p>
<p>我们需要让 <code>wrapper</code> 接收任意参数：</p>
<pre><code class="language-python">def log(func):
    def wrapper(*args, **kwargs):
        print("函数开始执行")
        result = func(*args, **kwargs)
        print("函数执行结束")
        return result
    return wrapper
</code></pre>
<p>使用：</p>
<pre><code class="language-python">@log
def greet(name):
    print(f"Hello, {name}")


greet("Mu-Miao")
</code></pre>
<p>输出：</p>
<pre><code class="language-text">函数开始执行
Hello, Mu-Miao
函数执行结束
</code></pre>
<p>这里的：</p>
<pre><code class="language-python">*args
**kwargs
</code></pre>
<p>表示可以接收任意位置参数和关键字参数。</p>
<p>这样装饰器就更通用了。</p>
<h2>保留原函数信息</h2>
<p>还有一个小问题。</p>
<p>看下面这段代码：</p>
<pre><code class="language-python">print(greet.__name__)
</code></pre>
<p>你可能希望输出：</p>
<pre><code class="language-text">greet
</code></pre>
<p>但实际可能输出：</p>
<pre><code class="language-text">wrapper
</code></pre>
<p>因为经过装饰后，<code>greet</code> 实际上已经变成了 <code>wrapper</code>。</p>
<p>为了解决这个问题，可以使用 <code>functools.wraps</code>：</p>
<pre><code class="language-python">from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("函数开始执行")
        result = func(*args, **kwargs)
        print("函数执行结束")
        return result
    return wrapper
</code></pre>
<p>这样就能保留原函数的名字、文档字符串等信息。</p>
<p>实际开发中，写装饰器时建议加上 <code>@wraps(func)</code>。</p>
<h2>一个实用例子：统计函数运行时间</h2>
<p>装饰器常用于日志、权限校验、缓存、性能统计等场景。</p>
<p>比如统计函数运行耗时：</p>
<pre><code class="language-python">import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 执行耗时：{end - start:.4f} 秒")
        return result
    return wrapper
</code></pre>
<p>使用：</p>
<pre><code class="language-python">@timer
def slow_task():
    time.sleep(1)
    print("任务完成")


slow_task()
</code></pre>
<p>输出：</p>
<pre><code class="language-text">任务完成
slow_task 执行耗时：1.0012 秒
</code></pre>
<p>这个例子里，<code>slow_task</code> 本身只负责做任务，计时逻辑由装饰器负责。</p>
<p>这就是装饰器的好处：把额外逻辑和核心逻辑分开。</p>
<h2>带参数的装饰器</h2>
<p>有时候，我们希望装饰器本身也能接收参数。</p>
<p>比如：</p>
<pre><code class="language-python">@repeat(3)
def say_hi():
    print("Hi")
</code></pre>
<p>希望函数执行 3 次。</p>
<p>这时需要多包一层函数：</p>
<pre><code class="language-python">from functools import wraps

def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator
</code></pre>
<p>使用：</p>
<pre><code class="language-python">@repeat(3)
def say_hi():
    print("Hi")


say_hi()
</code></pre>
<p>输出：</p>
<pre><code class="language-text">Hi
Hi
Hi
</code></pre>
<p>这里有三层函数：</p>
<pre><code class="language-python">repeat(times)
decorator(func)
wrapper(*args, **kwargs)
</code></pre>
<p>可以这样理解：</p>
<ul>
<li><p><code>repeat(3)</code> 接收装饰器参数</p>
</li>
<li><p><code>decorator(func)</code> 接收被装饰的函数</p>
</li>
<li><p><code>wrapper()</code> 执行增强后的逻辑</p>
</li>
</ul>
<h2>装饰器常见用途</h2>
<p>装饰器在实际开发中非常常见，比如：</p>
<pre><code class="language-python">@login_required
def user_profile():
    ...
</code></pre>
<p>表示访问这个函数前需要登录。</p>
<pre><code class="language-python">@cache
def get_data():
    ...
</code></pre>
<p>表示缓存函数结果。</p>
<pre><code class="language-python">@timer
def train_model():
    ...
</code></pre>
<p>表示统计函数执行时间。</p>
<p>在 Flask、FastAPI、Django 等框架里，装饰器也经常出现：</p>
<pre><code class="language-python">@app.route("/hello")
def hello():
    return "Hello"
</code></pre>
<p>这里的 <code>@app.route("/hello")</code> 本质上也是一个装饰器。</p>
<h2>总结</h2>
<p>Python 装饰器看起来神秘，但核心思想并不复杂。</p>
<p>一句话总结：</p>
<blockquote>
<p>装饰器是一个接收函数、返回函数的函数，用来在不修改原函数代码的情况下增强函数功能。</p>
</blockquote>
<p>它的基本形式是：</p>
<pre><code class="language-python">def decorator(func):
    def wrapper(*args, **kwargs):
        # 执行前的逻辑
        result = func(*args, **kwargs)
        # 执行后的逻辑
        return result
    return wrapper
</code></pre>
<p>再配合 <code>@decorator</code> 语法，就可以写出非常简洁、优雅、可复用的代码。</p>
<p>刚开始学装饰器时，不用急着记住所有复杂写法。先记住这句话就够了：</p>
<pre><code class="language-python">@log
def func():
    ...
</code></pre>
<p>等价于：</p>
<pre><code class="language-python">func = log(func)
</code></pre>
<p>理解了这一点，装饰器就不再是魔法，而只是 Python 给函数穿上的一件外套。</p>
]]></content:encoded></item></channel></rss>