<?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>Python Basics on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/python-basics/</link>
    <description>KbWen 的個人技術部落格，分享 Python、機器學習、深度學習、資料工程與 AI 開發的學習筆記與實作心得。</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>Sun, 31 May 2026 11:30:00 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/python-basics/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Python List Comprehensions: Read Them as For-Loops</title>
      <link>https://www.kbwen.com/python-list-comprehension-explained/</link>
      <pubDate>Sun, 31 May 2026 11:30:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-list-comprehension-explained/</guid>
      <description>A relaxed take on Python list comprehensions: translate them back into the equivalent for-loop, and check what&amp;#39;s actually true about variable leaking and speed on Python 3.14.</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong>: A list comprehension like <code>[n*n for n in range(5)]</code> does the same thing as a small for-loop. It just writes the <em>result</em> first and the <em>source</em> second, which is the opposite of the order you&rsquo;d write the loop in. If something trips you up, it&rsquo;s probably that reversal, not the concept. Translate it back into a for-loop and most of the mystery tends to go away.</p>
</blockquote>
<p>Seeing <code>[x for x in data if x &gt; 0]</code> for the first time and pausing for a second seems like a pretty normal reaction. It doesn&rsquo;t look like the statements you&rsquo;ve been writing. No colon, no indentation, and the <code>for</code> has wandered into the middle. Plenty of tutorials just say &ldquo;this is a list comprehension, it&rsquo;s very Pythonic&rdquo; and move on, but I&rsquo;m not sure that line actually helps anyone read the thing.</p>
<p>So instead of memorising the syntax, it might be easier to start from the for-loop you probably already know.</p>
<h2 id="the-same-thing-two-ways">The same thing, two ways</h2>
<p>Say you want a list of squares from 0 to 4. With a for-loop it looks roughly like this:</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">squares</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">squares</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 1, 4, 9, 16]</span>
</span></span></code></pre></div><p>Three lines. Make an empty list, run the loop, append one at a time. Nothing fancy, and it runs.</p>
<p>The comprehension version:</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">squares</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 1, 4, 9, 16]</span>
</span></span></code></pre></div><p>One line, same result. That&rsquo;s not just me saying so. There&rsquo;s a small test at the bottom that feeds both versions into <code>assertEqual</code>, and they come out equal. So I&rsquo;d say it&rsquo;s safe to treat the comprehension as shorthand for that loop, because that&rsquo;s more or less what it is, squeezed onto one line.</p>
<h2 id="how-to-read-one-translate-it-back">How to read one: translate it back</h2>
<p>The thing that matters here, I think, is reading order. A comprehension looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="p">[</span>  <span class="n">expression</span>   <span class="k">for</span> <span class="k">var</span> <span class="ow">in</span> <span class="n">source</span>  <span class="p">]</span>
</span></span><span class="line"><span class="cl">   <span class="n">n</span> <span class="o">*</span> <span class="n">n</span>         <span class="k">for</span>  <span class="n">n</span>  <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span></code></pre></div><p>Three blocks:</p>
<ul>
<li><code>for n in range(5)</code>, same as the start of a normal loop, &ldquo;pull items out of range(5), call each one n&rdquo;</li>
<li><code>n * n</code>, what each round produces, basically the thing inside <code>append()</code></li>
<li>the outer <code>[ ]</code>, collect it all into a list</li>
</ul>
<p>My guess is that people get stuck because the eye expects &ldquo;for first, then do something&rdquo;, but a comprehension is the other way round: result first, then where it came from. Reading it back-to-front can help: glance at the <code>for ... in ...</code> in the middle to see the source, then look back at the front block. After a few of these it stops feeling weird, at least it did for me.</p>
<p>If you&rsquo;ve read the earlier <a href="/python-chunks/">Python Chunks</a> post, it quietly used a comprehension to slice a list (<code>[input_list[i:i+n] for i in range(0, len(input_list), n)]</code>) without really explaining it. That line might read a little easier now.</p>
<h2 id="filtering-put-the-if-at-the-end">Filtering: put the if at the end</h2>
<p>You can tack an <code>if</code> onto the end as a filter. Say you only want even numbers:</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">evens</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 2, 4, 6, 8]</span>
</span></span></code></pre></div><p>Translate it back and it&rsquo;s roughly:</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">evens</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">evens</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</span></span></code></pre></div><p>The trailing <code>if</code> acts like a gate: produce a value if the condition holds, skip it otherwise. So the result comes out shorter than the source. This is the use I reach for most often, pulling the few items that match out of a pile.</p>
<h2 id="the-part-people-mix-up-trailing-if-vs-leading-ifelse">The part people mix up: trailing if vs leading if/else</h2>
<p>This is the bit I find easiest to confuse, so it&rsquo;s worth pulling apart. The <code>if</code> above sits at the end and asks &ldquo;keep this one or not&rdquo;. But the moment you write <code>if/else</code>, it jumps to the <em>front</em> and means something different:</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">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fizz&#34;</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="s2">&#34;buzz&#34;</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [&#39;fizz&#39;, &#39;buzz&#39;, &#39;fizz&#39;, &#39;buzz&#39;, &#39;fizz&#39;, &#39;buzz&#39;]</span>
</span></span></code></pre></div><p>Six results, none dropped. That&rsquo;s because <code>&quot;fizz&quot; if ... else &quot;buzz&quot;</code> is a conditional (ternary) expression. It <em>is</em> the &ldquo;expression&rdquo; block, and it always returns one value, just a different one depending on the condition. So it&rsquo;s not filtering; it&rsquo;s more like &ldquo;produce one for every item, they just look different&rdquo;.</p>
<p>A rough way to keep them apart:</p>
<ul>
<li><code>[x for x in xs if cond]</code>, <code>if</code> at the end, filters, result may be shorter</li>
<li><code>[a if cond else b for x in xs]</code>, <code>if/else</code> at the front, produces every item, same length</li>
</ul>
<p>Mixing these two up seems fairly common, and I still pause sometimes to work out which one I&rsquo;m looking at. When I genuinely can&rsquo;t tell, translating it back to a loop usually settles it.</p>
<h2 id="two-side-notes-the-loop-variable-doesnt-leak-and-its-not-really-faster">Two side notes: the loop variable doesn&rsquo;t leak, and&hellip; it&rsquo;s not really faster</h2>
<p>Here&rsquo;s something that maybe doesn&rsquo;t get noticed much: the loop variable inside a comprehension doesn&rsquo;t survive afterwards. Compare with a plain for-loop:</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">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>        <span class="c1">## 2 — m is still around, in the outer scope</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">_</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>        <span class="c1">## NameError: name &#39;n&#39; is not defined</span>
</span></span></code></pre></div><p>A normal loop leaves <code>m</code> lingering in the current scope (that&rsquo;s been true throughout Python 3), while the comprehension&rsquo;s <code>n</code> is cleaned up once it finishes. One fewer variable you might accidentally reuse, a small upside, though honestly not something you&rsquo;d usually think about.</p>
<p>On performance, I want to flag one thing, because it seems to get passed around a lot. Older articles like to say comprehensions are &ldquo;twice as fast&rdquo;, but that figure is probably quite a few years old. On Python 3.14.3, timing it with <code>timeit</code> (<code>range(1000)</code>, 20,000 runs), a comprehension against an <code>append</code> loop comes out roughly like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">comprehension: 0.52s
</span></span><span class="line"><span class="cl">append loop  : 0.58s
</span></span><span class="line"><span class="cl">loop / comp  : about 1.1x
</span></span></code></pre></div><p>Only around ten percent, much smaller than the legend. My guess is that recent CPython&rsquo;s adaptive specializing interpreter (introduced in 3.11) optimises the <code>append</code> loop too. So rather than reaching for a comprehension &ldquo;because it&rsquo;s faster&rdquo;, I&rsquo;d lean on &ldquo;because it reads more cleanly&rdquo;. That gap probably won&rsquo;t show up in real code anyway. I haven&rsquo;t profiled this across many machines, so take the exact number as one data point rather than a universal constant.</p>
<h2 id="not-just-lists-dicts-and-sets-too">Not just lists: dicts and sets too</h2>
<p>Swap the outer brackets and the same ordering carries over to dictionaries and sets. Dict comprehensions came in via <a href="https://peps.python.org/pep-0274/">PEP 274</a>; the set comprehension <em>syntax</em> was added later, in Python 3.0 / 2.7. Slightly different origins, though they feel consistent to write.</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">word</span> <span class="o">=</span> <span class="s2">&#34;mississippi&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## set comprehension — dedupes along the way</span>
</span></span><span class="line"><span class="cl"><span class="n">unique</span> <span class="o">=</span> <span class="p">{</span><span class="n">ch</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">word</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">## {&#39;m&#39;, &#39;i&#39;, &#39;s&#39;, &#39;p&#39;}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## dict comprehension — key: value</span>
</span></span><span class="line"><span class="cl"><span class="n">counts</span> <span class="o">=</span> <span class="p">{</span><span class="n">ch</span><span class="p">:</span> <span class="n">word</span><span class="o">.</span><span class="n">count</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="nb">set</span><span class="p">(</span><span class="n">word</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="c1">## {&#39;m&#39;: 1, &#39;i&#39;: 4, &#39;s&#39;: 4, &#39;p&#39;: 2}</span>
</span></span></code></pre></div><p>One value inside <code>{ }</code> gives you a set; a <code>key: value</code> pair gives you a dict. Reading them works the same as a list, nothing new to pick up. The dict form is the one I use most, usually to zip two lists into a lookup table.</p>
<h2 id="a-bit-further-flattening-nests-and-the-walrus">A bit further: flattening nests, and the walrus</h2>
<p>Two that come up fairly often but are easy to write badly.</p>
<p>Flattening a 2-D list. Multiple <code>for</code> clauses read left to right, in the same order as nested loops:</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">matrix</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span> <span class="p">[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="n">flat</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">matrix</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">row</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">## [1, 2, 3, 4, 5, 6, 7, 8, 9]</span>
</span></span></code></pre></div><p>Read it as <code>for row in matrix</code> (outer), <code>for x in row</code> (inner), then <code>x</code> (produce). The written order matches outer-to-inner just like nested loops, so if the single line throws you, unpacking it in your head helps. If it stays confusing, I&rsquo;d just write the plain loop — no need to force one line.</p>
<p>The walrus operator <code>:=</code> came in with <a href="https://peps.python.org/pep-0572/">PEP 572</a>, Python 3.8 onward. When you want to filter <em>and</em> reuse the value you computed during filtering, it lets you compute it once:</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">data</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;  10 &#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34; 20&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;30 &#34;</span><span class="p">]</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">parse</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cleaned</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">:=</span> <span class="n">parse</span><span class="p">(</span><span class="n">s</span><span class="p">))</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">## [10, 20, 30]</span>
</span></span></code></pre></div><p><code>(v := parse(s))</code> stores the result in <code>v</code> and lets the trailing <code>if</code> test it, so <code>parse()</code> doesn&rsquo;t run twice. Handy, though to be honest this is also about the point where readability starts sliding, so whether to use it is a judgement call — I tend to hesitate a little.</p>
<h2 id="when-a-comprehension-probably-isnt-the-move">When a comprehension probably isn&rsquo;t the move</h2>
<p>More comprehensions isn&rsquo;t better, and forcing one can make things harder than a loop. A few cases where I&rsquo;d lean back toward a plain for-loop:</p>
<ul>
<li><strong>Nesting past two levels, or several <code>if</code>s stacked in</strong>: too much on one line, and you (or future you) might not parse it later. If it&rsquo;s unreadable, the comprehension has kind of lost the point.</li>
<li><strong>Side effects each round</strong>: writing files, <code>print</code>, firing a request. Comprehensions are really meant for <em>building a new collection</em>; writing <code>[do(x) for x in xs]</code> just for the side effect also builds a list you didn&rsquo;t want, which is a bit wasteful.</li>
<li><strong>Logic that needs intermediate variables or try/except</strong>: those don&rsquo;t fit inside a comprehension, and cramming them in usually looks worse.</li>
</ul>
<p>A rough test: could the person next to you read this line at a glance? If not, splitting it out is probably the kinder choice. The Zen of Python line &ldquo;Readability counts&rdquo; feels, to me, worth a bit more than the speed here.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>A list comprehension is maybe less mysterious than it looks — mostly it&rsquo;s shorthand for a for-loop, with the difference being reading order: result first, source second. Find the source in the middle <code>for ... in ...</code>, check the front block for what each item becomes, and watch whether the <code>if</code> sits at the end (filter) or the front as <code>if/else</code> (produce every item). Once those click, dicts, sets, nesting, and the walrus are pretty much the same ordering extended — not really separate things to learn.</p>
<p>If you want to wander through other small Python pieces, these are nearby: <a href="/python-lambda/">Python lambda</a> (the anonymous function comprehensions often appear with), <a href="/python-iterable/">Python Iterable</a> (what can actually go in the &ldquo;source&rdquo; slot), <a href="/python-f-string/">Python f-string</a> (another way to shorten code), and the practical <a href="/python-chunks/">Python Chunks</a> for slicing data.</p>
<p>There&rsquo;s also a <a href="/python-list-comprehension/">Traditional Chinese version of this post</a> if that reads more comfortably.</p>
<hr>
<p><em>All examples were run on Python 3.14.3; the performance figures come from <code>timeit</code> and will vary by machine, so treat them as directional rather than exact. Primary sources: <a href="https://peps.python.org/pep-0202/">PEP 202 — List Comprehensions</a>, <a href="https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions">Python tutorial 5.1.3</a>.</em></p>
<h2 id="appendix-the-test-file-i-mentioned">Appendix: the test file I mentioned</h2>
<p>Back when I said the loop and the comprehension give the same result, this is what checked it. It&rsquo;s short. On Python 3.14.3, <code>python -m unittest</code> runs green.</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">unittest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">by_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">out</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">out</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">by_comprehension</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">TestSame</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">test_two_ways_match</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># same thing, two ways — results should match</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">by_loop</span><span class="p">(),</span> <span class="n">by_comprehension</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">by_comprehension</span><span class="p">(),</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">16</span><span class="p">])</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">test_tail_if_filters</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># trailing if filters; evens survive</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">([</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">])</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">test_if_else_keeps_length</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># leading if/else produces every item; length unchanged</span>
</span></span><span class="line"><span class="cl">        <span class="n">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fizz&#34;</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="s2">&#34;buzz&#34;</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">labels</span><span class="p">),</span> <span class="mi">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span>
</span></span></code></pre></div><p>Nothing clever — it just pins down the three claims from earlier (&ldquo;two ways are equivalent&rdquo;, &ldquo;trailing if shortens&rdquo;, &ldquo;leading if/else keeps the length&rdquo;) with <code>assertEqual</code>. If a future Python release breaks one of them, this is what would flag it first.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Python 列表推導式：一行取代 for 迴圈</title>
      <link>https://www.kbwen.com/python-list-comprehension/</link>
      <pubDate>Sun, 31 May 2026 11:00:00 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-list-comprehension/</guid>
      <description>用比較白話的方式聊 Python 列表推導式：把它翻回普通的 for 迴圈來看，順便用 Python 3.14 實測一下變數外洩跟效能到底是怎樣。</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong>：列表推導式 <code>[n*n for n in range(5)]</code> 其實就跟一個 for 迴圈做一樣的事，只是把「結果」寫在最前面、「來源」丟到後面，順序剛好跟念中文相反。會看不懂多半不是因為它難，比較像是這個順序要花點時間習慣。能把它翻回 for 迴圈來看的話，大概就沒那麼可怕了。</p>
</blockquote>
<p>第一次看到 <code>[x for x in data if x &gt; 0]</code> 這種東西會愣一下，我覺得滿正常的。它長得不太像一般的句子，沒冒號、沒縮排，<code>for</code> 還跑到中間去。很多地方會直接說「這叫列表推導式（list comprehension），很 Pythonic」就帶過，可是那句話其實對看懂它沒什麼幫助，看完還是一樣霧。</p>
<p>所以這篇就不背語法了，從大家應該都會的 for 迴圈開始慢慢聊好了，不趕時間。</p>
<h2 id="同一件事兩種寫法">同一件事，兩種寫法</h2>
<p>假設要做一個 0 到 4 的平方數清單。用 for 迴圈大概像這樣寫：</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">squares</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">squares</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 1, 4, 9, 16]</span>
</span></span></code></pre></div><p>三行，開個空 list、跑迴圈、一個一個 <code>append</code> 進去。很普通，沒什麼問題，能跑就好。</p>
<p>換成列表推導式的話，就變這樣：</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">squares</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 1, 4, 9, 16]</span>
</span></span></code></pre></div><p>一行，結果一樣。這倒不是我隨口說的，文末附的測試檔就是把這兩種寫法的結果丟去 <code>assertEqual</code> 對，跑出來是相等的。所以大概可以放心把它當成上面那段 for 迴圈的縮寫，因為它字面上差不多就是那個意思，只是擠成一行而已。</p>
<h2 id="怎麼讀它翻回-for-迴圈來看">怎麼讀它：翻回 for 迴圈來看</h2>
<p>我覺得重點在閱讀順序。推導式長這樣：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">[  運算式   for 變數 in 來源  ]
</span></span><span class="line"><span class="cl">   n * n    for  n  in range(5)
</span></span></code></pre></div><p>拆成三塊來看的話：</p>
<ul>
<li><code>for n in range(5)</code>：跟一般迴圈的開頭一樣，「從 range(5) 一個一個拿出來叫 n」</li>
<li><code>n * n</code>：每一輪要產出什麼，差不多就是 <code>append()</code> 括號裡那個東西</li>
<li>外面的 <code>[ ]</code>：最後裝成一個 list</li>
</ul>
<p>我猜會卡住，大概是因為眼睛習慣「先 for 再做事」，但推導式是反過來的，先寫結果、再講它從哪來。讀的時候在心裡把順序倒過來看可能會好一點：先瞄中間的 <code>for ... in ...</code> 知道資料哪來的，再回頭看最前面那塊。多看幾次好像就習慣了，我自己現在是不太需要停下來想。</p>
<p>其實如果有看過之前那篇 <a href="/python-chunks/">Python Chunks</a>，裡面切 list 的時候就偷用過推導式（<code>[input_list[i:i+n] for i in range(0, len(input_list), n)]</code>），只是那時候沒特別解釋。現在回去看那行，搞不好會順眼一點。</p>
<h2 id="想過濾的話if-放尾巴">想過濾的話，if 放尾巴</h2>
<p>推導式最後面可以接一個 <code>if</code> 當過濾器。比如說只想留偶數：</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">evens</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [0, 2, 4, 6, 8]</span>
</span></span></code></pre></div><p>一樣翻回 for 迴圈看就懂了，它大概等於：</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">evens</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">evens</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</span></span></code></pre></div><p>尾巴的 <code>if</code> 比較像一個閘門，條件成立才產出，不成立就跳過，所以結果長度會比來源短一點。這個用法我自己滿常用的，像是想從一堆東西裡撈出符合條件的那幾個，寫一行就清掉了。</p>
<h2 id="比較容易搞混的尾巴的-if-跟前面的-ifelse">比較容易搞混的：尾巴的 if 跟前面的 if/else</h2>
<p>這個我自己覺得是最容易混的地方，分開講一下好了。上面那個 <code>if</code> 在尾巴，是在問「這筆要不要」。可是只要出現 <code>if/else</code>，位置會跑到最前面，意思也跟著不一樣了：</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">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fizz&#34;</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="s2">&#34;buzz&#34;</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## [&#39;fizz&#39;, &#39;buzz&#39;, &#39;fizz&#39;, &#39;buzz&#39;, &#39;fizz&#39;, &#39;buzz&#39;]</span>
</span></span></code></pre></div><p>結果有六個、一個都沒少。因為 <code>&quot;fizz&quot; if ... else &quot;buzz&quot;</code> 是一個三元運算式，它本身就是「運算式」那一塊，一定會吐一個值出來，只是吐哪個看條件。所以它不是在篩選，比較像是「每筆都會產，只是長相不同」。</p>
<p>大概可以這樣分：</p>
<ul>
<li><code>[x for x in xs if 條件]</code>，<code>if</code> 在尾巴，是篩選，結果可能變短</li>
<li><code>[a if 條件 else b for x in xs]</code>，<code>if/else</code> 在前面，每筆都產，結果一樣長</li>
</ul>
<p>這兩個搞混好像滿常見的，我自己偶爾也要停下來想一下到底是哪個。真的記不起來的話，就回去翻成 for 迴圈，一翻就現形了。</p>
<h2 id="順帶提兩個小地方變數不外洩還有其實沒快多少">順帶提兩個小地方：變數不外洩，還有……其實沒快多少</h2>
<p>有件事可能不少人沒注意到：推導式裡的迴圈變數跑完不會留在外面。跟普通 for 迴圈對照一下就看得出來：</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">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>        <span class="c1">## 2，m 還在，留在外層</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">_</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>        <span class="c1">## NameError: name &#39;n&#39; is not defined</span>
</span></span></code></pre></div><p>普通迴圈跑完 <code>m</code> 會殘留在當前作用域（Python 3 一直都這樣），推導式的 <code>n</code> 跑完就被收掉了。少一個可能會誤用到的變數，算是個小小的好處吧，雖然平常大概也不太會去注意。</p>
<p>至於效能，這裡想順便講一下，因為好像有點以訛傳訛。很多舊文章會說推導式「快兩倍」，但那大概是滿多年前的數字了。我在 Python 3.14.3 上用 <code>timeit</code> 試了一下（<code>range(1000)</code>、跑兩萬次），推導式對上 <code>append</code> 迴圈差不多是這樣：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">comprehension: 0.52s
</span></span><span class="line"><span class="cl">append loop  : 0.58s
</span></span><span class="line"><span class="cl">loop / comp  : 大概 1.1 倍
</span></span></code></pre></div><p>只快一成左右，比傳說中小很多。我猜是因為新版 CPython 那個 adaptive specializing interpreter（3.11 開始有的）把 <code>append</code> 迴圈也順便優化了。所以與其說「為了快」用推導式，不如說是「因為這樣比較好讀」才用它，那點差距在真的程式裡大概也量不太出來。</p>
<h2 id="不只是-listdict-跟-set-也可以">不只是 list，dict 跟 set 也可以</h2>
<p>把外面的括號換掉，同一套順序就搬到字典跟集合上了。dict 推導式是 <a href="https://peps.python.org/pep-0274/">PEP 274</a> 帶進來的；set 推導式的語法則是 Python 3.0 / 2.7 那時候才補上，兩個來源不太一樣，不過寫起來感覺是一致的。</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">word</span> <span class="o">=</span> <span class="s2">&#34;mississippi&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## set 推導式，順便去重</span>
</span></span><span class="line"><span class="cl"><span class="n">unique</span> <span class="o">=</span> <span class="p">{</span><span class="n">ch</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="n">word</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="c1">## {&#39;m&#39;, &#39;i&#39;, &#39;s&#39;, &#39;p&#39;}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## dict 推導式，key: value</span>
</span></span><span class="line"><span class="cl"><span class="n">counts</span> <span class="o">=</span> <span class="p">{</span><span class="n">ch</span><span class="p">:</span> <span class="n">word</span><span class="o">.</span><span class="n">count</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="k">for</span> <span class="n">ch</span> <span class="ow">in</span> <span class="nb">set</span><span class="p">(</span><span class="n">word</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="c1">## {&#39;m&#39;: 1, &#39;i&#39;: 4, &#39;s&#39;: 4, &#39;p&#39;: 2}</span>
</span></span></code></pre></div><p><code>{ }</code> 裡只放一個值就是 set，有 <code>key: value</code> 就是 dict。讀法跟 list 一樣，沒什麼新東西要學，就是括號換一下而已。我自己最常用的是 dict 推導式，拿來把兩個 list 兜成一個對照表很順手。</p>
<h2 id="再進階一點攤平巢狀還有海象運算子">再進階一點：攤平巢狀、還有海象運算子</h2>
<p>兩個還算常用、但有點容易寫歪的，順便講講。</p>
<p>攤平二維 list。多個 <code>for</code> 從左排到右，順序跟巢狀 for 迴圈一樣：</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">matrix</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span> <span class="p">[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="n">flat</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">matrix</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">row</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">## [1, 2, 3, 4, 5, 6, 7, 8, 9]</span>
</span></span></code></pre></div><p>讀法是 <code>for row in matrix</code>（外層）、<code>for x in row</code>（內層）、然後 <code>x</code>（產出）。寫的順序跟巢狀迴圈由外到內一樣，被它擠在一行嚇到的話，拆開來想就還好。這個我建議真的搞不清楚就先寫普通迴圈，沒必要硬擠一行。</p>
<p>海象運算子 <code>:=</code> 是 <a href="https://peps.python.org/pep-0572/">PEP 572</a> 帶進來的，Python 3.8 之後才有。如果你又想過濾、又想用「過濾時順便算出來的那個值」，它可以讓你只算一次：</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">data</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;  10 &#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34; 20&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;30 &#34;</span><span class="p">]</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">parse</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">if</span> <span class="n">s</span><span class="o">.</span><span class="n">isdigit</span><span class="p">()</span> <span class="k">else</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cleaned</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">data</span> <span class="k">if</span> <span class="p">(</span><span class="n">v</span> <span class="o">:=</span> <span class="n">parse</span><span class="p">(</span><span class="n">s</span><span class="p">))</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">## [10, 20, 30]</span>
</span></span></code></pre></div><p><code>(v := parse(s))</code> 把結果存進 <code>v</code>，順便讓尾巴的 <code>if</code> 拿去判斷，這樣就不用 <code>parse()</code> 跑兩遍。滿方便的，不過老實說這也差不多是可讀性開始往下掉的訊號了，要不要用自己感覺一下，我自己是會稍微猶豫。</p>
<h2 id="什麼時候可能不太適合用">什麼時候可能不太適合用</h2>
<p>推導式我覺得不是越多越好，有時候硬要用反而把事情弄複雜。下面幾種狀況，我自己會傾向退回普通 for 迴圈：</p>
<ul>
<li><strong>巢狀超過兩層、或夾好幾個 if</strong>：一行塞太滿，可能過陣子連自己都讀不太懂。讀不懂的話，用它好像就有點失去意義了。</li>
<li><strong>每一輪有副作用</strong>：像寫檔、<code>print</code>、發 request 那種。推導式本來比較像是拿來「生一個新集合」用的，如果只是為了做一串動作而寫成 <code>[do(x) for x in xs]</code>，還會順手做出一個你根本不要的 list，有點浪費。</li>
<li><strong>邏輯複雜到要中間變數、try/except</strong>：這些推導式裡塞不太進去，硬塞通常只會更難看。</li>
</ul>
<p>大概的判斷方式：這行寫出來，旁邊的人掃一眼讀得懂嗎？讀不懂的話拆開可能比較舒服。Python 之禪那句「Readability counts」，在這種地方我覺得是比效能值錢一點的。</p>
<h2 id="附剛剛說的那個測試檔">附：剛剛說的那個測試檔</h2>
<p>前面講「推導式跟 for 迴圈結果一樣」的時候，說有丟去對過。就是這個，放這邊給有興趣的人看一下，其實也沒幾行。在 Python 3.14.3 上 <code>python -m unittest</code> 跑是全綠的。</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">unittest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">by_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">out</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">out</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">by_comprehension</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">n</span> <span class="o">*</span> <span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">TestSame</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">test_two_ways_match</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 同一件事的兩種寫法, 結果應該一模一樣</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">by_loop</span><span class="p">(),</span> <span class="n">by_comprehension</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">by_comprehension</span><span class="p">(),</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">16</span><span class="p">])</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">test_tail_if_filters</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># 尾巴的 if 是篩選, 偶數留下來</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">([</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">])</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">test_if_else_keeps_length</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># if/else 在前面, 每筆都產, 長度不變</span>
</span></span><span class="line"><span class="cl">        <span class="n">labels</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fizz&#34;</span> <span class="k">if</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="s2">&#34;buzz&#34;</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">labels</span><span class="p">),</span> <span class="mi">6</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">unittest</span><span class="o">.</span><span class="n">main</span><span class="p">()</span>
</span></span></code></pre></div><p>沒什麼特別的，就是把「兩種寫法等價」「尾巴 if 會篩短」「前面 if/else 不改長度」這三件前面講過的事，用 <code>assertEqual</code> 釘住而已。哪天 Python 改版改壞了，這個會先跳給你看。</p>
<h2 id="小結">小結</h2>
<p>列表推導式好像也沒那麼玄，大致上就是 for 迴圈的縮寫，差別主要在閱讀順序，結果在前、來源在後。先看中間的 <code>for ... in ...</code> 找來源，再看最前面那塊看每筆怎麼變，然後留意一下 <code>if</code> 在尾巴是過濾、<code>if/else</code> 在前面是每筆都產。這幾個搞清楚之後，dict、set、巢狀、海象大概都是同一套順序的延伸而已，不算另外的東西。</p>
<p>想再翻翻 Python 其他語法小品的話，這幾篇可以順手看看：<a href="/python-lambda/">Python lambda</a>（推導式裡常一起出現的匿名函式）、<a href="/python-iterable/">Python Iterable</a>（推導式的「來源」到底能放哪些東西）、<a href="/python-f-string/">Python f-string</a>（另一個讓程式變短的小工具），還有把它拿去切資料的 <a href="/python-chunks/">Python Chunks</a>。</p>
<p>想看英文版的話，這裡也有一篇 <a href="/python-list-comprehension-explained/">English version of this post</a>。</p>
<hr>
<p><em>本文範例都在 Python 3.14.3 上跑過；效能數字是用 <code>timeit</code> 量的，換機器應該會有出入。想看源頭的話：<a href="https://peps.python.org/pep-0202/">PEP 202 — List Comprehensions</a>、<a href="https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions">Python 官方教學 5.1.3</a>。</em></p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
