<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Functools on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/functools/</link>
    <description>KbWen is a practical technology blog about AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Thu, 30 Jul 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/functools/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>What functools.wraps restores when you decorate a function</title>
      <link>https://www.kbwen.com/python-decorators-functools-wraps/</link>
      <pubDate>Thu, 30 Jul 2026 09:00:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-decorators-functools-wraps/</guid>
      <description>A decorator replaces your function with a wrapper, so its name, docstring, and signature change. Here is exactly what functools.wraps copies back and how it records __wrapped__.</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR:</strong> Put <code>@functools.wraps(func)</code> on every wrapper function you write. Without it the decorated function reports the wrapper&rsquo;s <code>__name__</code>, loses its docstring, and shows a <code>(*args, **kwargs)</code> signature. With it, Python copies the original&rsquo;s identifying attributes across and stores the original as <code>__wrapped__</code>, so <code>inspect.signature</code> still finds the real function.</p>
</blockquote>
<p>When you decorate a function, the name you defined ends up bound to a different object. The decorator returns a wrapper, Python binds your original name to that wrapper, and every tool that introspects the function — a traceback, <code>help()</code>, <code>inspect.signature</code> — reads the wrapper instead of the function you wrote.</p>
<p>Here is the smallest decorator that shows it. <code>trace</code> takes a function and returns a new function, <code>wrapper</code>, that calls through to the original:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">functools</span><span class="o">,</span> <span class="nn">inspect</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">trace</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">wrapper</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@trace</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">greet</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Return a greeting for name.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Hello, </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>The <code>@trace</code> line is shorthand for <code>greet = trace(greet)</code>; like <a href="/python-list-comprehension-explained/">a list comprehension</a>, it is sugar you can read back into ordinary code. After it runs, the name <code>greet</code> points at <code>wrapper</code>. The function you wrote is still there, but only as the object <code>wrapper</code> calls through to. Ask the name about itself and it answers as the wrapper:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">greet</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="cl"><span class="s1">&#39;wrapper&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">greet</span><span class="o">.</span><span class="vm">__doc__</span>          <span class="c1"># prints nothing: it is None</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">greet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">Signature</span> <span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span><span class="o">&gt;</span>
</span></span></code></pre></div><p>The name is <code>'wrapper'</code>, the docstring is <code>None</code>, and the signature is the wrapper&rsquo;s <code>(*args, **kwargs)</code> rather than <code>(name)</code>. Nothing copied the original function&rsquo;s metadata onto the wrapper, so there is nothing for it to report but its own. <code>help</code> reads the same attributes, so it describes the wrapper too:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">help</span><span class="p">(</span><span class="n">greet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Help</span> <span class="n">on</span> <span class="n">function</span> <span class="n">wrapper</span> <span class="ow">in</span> <span class="n">module</span> <span class="n">__main__</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span></code></pre></div><p>In a traceback or in generated documentation, this function is now hard to tell apart from every other <code>wrapper</code> in the codebase.</p>
<h2 id="adding-functoolswraps">Adding <code>functools.wraps</code></h2>
<p><code>functools.wraps</code> is a decorator you apply to the wrapper. One line changes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">trace</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@functools.wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">wrapper</span>
</span></span></code></pre></div><p>Decorate <code>greet</code> again with this version and it answers as itself:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">greet</span><span class="o">.</span><span class="vm">__name__</span>
</span></span><span class="line"><span class="cl"><span class="s1">&#39;greet&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">greet</span><span class="o">.</span><span class="vm">__doc__</span>
</span></span><span class="line"><span class="cl"><span class="s1">&#39;Return a greeting for name.&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">inspect</span><span class="o">.</span><span class="n">signature</span><span class="p">(</span><span class="n">greet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">Signature</span> <span class="p">(</span><span class="n">name</span><span class="p">)</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">greet</span><span class="o">.</span><span class="n">__wrapped__</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">function</span> <span class="n">greet</span> <span class="n">at</span> <span class="mi">0</span><span class="n">x</span><span class="o">...&gt;</span>
</span></span></code></pre></div><p>The name is back, the docstring is back, and the signature reports <code>(name)</code> again even though the wrapper is still literally defined as <code>(*args, **kwargs)</code>. <code>help</code> finds the original everywhere it looks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">help</span><span class="p">(</span><span class="n">greet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Help</span> <span class="n">on</span> <span class="n">function</span> <span class="n">greet</span> <span class="ow">in</span> <span class="n">module</span> <span class="n">__main__</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">greet</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">Return</span> <span class="n">a</span> <span class="n">greeting</span> <span class="k">for</span> <span class="n">name</span><span class="o">.</span>
</span></span></code></pre></div><p>The name and docstring came back together; the signature came back for a different reason.</p>
<h2 id="what-wraps-copies">What <code>@wraps</code> copies</h2>
<p>The functools source lists exactly what gets copied. <code>wraps</code> is a thin wrapper over <code>functools.update_wrapper</code>, whose own one-line summary is &ldquo;Update a wrapper function to look like the wrapped function.&rdquo; It copies a fixed tuple of attributes from the original onto the wrapper — in Python 3.13:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">WRAPPER_ASSIGNMENTS</span> <span class="o">=</span> <span class="p">(</span><span class="s1">&#39;__module__&#39;</span><span class="p">,</span> <span class="s1">&#39;__name__&#39;</span><span class="p">,</span> <span class="s1">&#39;__qualname__&#39;</span><span class="p">,</span> <span class="s1">&#39;__doc__&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                       <span class="s1">&#39;__annotations__&#39;</span><span class="p">,</span> <span class="s1">&#39;__type_params__&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p><code>__name__</code> and <code>__doc__</code> are on that list, which is why they came back. <code>update_wrapper</code> also merges the original&rsquo;s <code>__dict__</code> (that is <code>WRAPPER_UPDATES = ('__dict__',)</code>) into the wrapper&rsquo;s, so any attributes you had set on the function survive the wrapping.</p>
<p>The tuple is worth reading on your own interpreter rather than trusting a copy of it, because it does move between versions: 3.14 swapped <code>__annotations__</code> for <code>__annotate__</code>, so on a current Python the same line reads <code>('__module__', '__name__', '__qualname__', '__doc__', '__annotate__', '__type_params__')</code>. Whatever is in the tuple is what comes across.</p>
<p>The signature is the second thing, and it does not come from that tuple. <code>update_wrapper</code> runs one more line:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">wrapper</span><span class="o">.</span><span class="n">__wrapped__</span> <span class="o">=</span> <span class="n">wrapped</span>
</span></span></code></pre></div><p>It stores the original function on the wrapper as <code>__wrapped__</code>. <code>inspect.signature</code> looks for that attribute and follows it, so with <code>@wraps</code> in place, asking the decorated function for its signature reaches past the wrapper&rsquo;s <code>(*args, **kwargs)</code> to the real <code>(name)</code>. The docs give the reason the reference is kept: &ldquo;To allow access to the original function for introspection and other purposes (e.g. bypassing a caching decorator such as <code>lru_cache</code>), this function automatically adds a <code>__wrapped__</code> attribute to the wrapper that refers to the function being wrapped.&rdquo;</p>
<p>At call time the wrapper behaves the same with or without <code>wraps</code>; nothing about how the function runs changes. What <code>wraps</code> touches is only the function&rsquo;s report of itself, and the <code>__wrapped__</code> it leaves behind is the handle other code follows back to the original — <code>inspect.signature</code> uses it, and so does anything that needs to see past a wrapper like <code>lru_cache</code>. The <a href="https://docs.python.org/3/library/functools.html">functools documentation</a> lists the full set of copied attributes.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
