<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Python on 止语Lab</title>
        <link>https://www.wujiachen.com.cn/tags/python/</link>
        <description>Recent content in Python on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Sat, 30 May 2026 11:05:01 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/python/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>为什么 Python 的简单越到工程里越贵？</title>
            <link>https://www.wujiachen.com.cn/posts/python-simplicity-cost/</link>
            <pubDate>Sat, 30 May 2026 11:05:00 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/python-simplicity-cost/</guid>
            <description>&lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/cover.png&#34; alt=&#34;Featured image of post 为什么 Python 的简单越到工程里越贵？&#34; /&gt;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/cover.png&#34; alt=&#34;封面&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Python 最迷人的地方，是它让第一步几乎没有摩擦。&lt;/p&gt;&#xA;&lt;p&gt;你想处理一个 CSV，十几行就能跑。你想调一个接口，装个库就能试。你想把一段想法变成程序，不需要先和类型系统、构建系统、包结构、工程模板打半小时交道。&lt;/p&gt;&#xA;&lt;p&gt;这是真的。&lt;/p&gt;&#xA;&lt;p&gt;但很多工程里的误判，也从这里开始。&lt;/p&gt;&#xA;&lt;p&gt;我们很容易把&amp;quot;脚本能跑&amp;quot;，误认为&amp;quot;项目已经具备长期维护的边界&amp;quot;。Python 的简单不是消灭复杂度，而是改变复杂度结算的位置。语法层少付的，常常会在运行时、类型检查、测试、依赖管理和交付流程里重新出现。&lt;/p&gt;&#xA;&lt;p&gt;我把一个单文件脚本推到最小可交付项目，文件从 1 个变成 8 个，命令从 1 条变成 5 条，配置项从 0 增长到 6。这就是后面要拆的账。&lt;/p&gt;&#xA;&lt;p&gt;为什么是 Python 特别容易把这笔账后置？三个机制：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;动态类型默认不强制声明——你可以一路写到底，直到运行时才遇见类型矛盾。&lt;/li&gt;&#xA;&lt;li&gt;项目结构、依赖声明、类型检查全是可选的——&lt;code&gt;pyproject.toml&lt;/code&gt;、&lt;code&gt;venv&lt;/code&gt;、&lt;code&gt;mypy&lt;/code&gt; 没有一个是默认强制的。&lt;/li&gt;&#xA;&lt;li&gt;单文件直接 &lt;code&gt;python xxx.py&lt;/code&gt; 即可运行——没有编译、没有打包、没有构建前置步骤。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;静态语言在第一步就要求你付这些成本：声明类型、建项目结构、走构建流程。Python 把选择权留给你，但选择权也意味着延迟的可能。项目越小，延迟越无害；项目越接近团队协作和长期交付，这些被推迟的约束越会以更高的代价回来。&lt;/p&gt;&#xA;&lt;p&gt;我不想把这篇写成一篇反 Python 的檄文。那种写法很省事，也很无聊。真正值得拆的是：Python 的简单到底在哪一层最值钱？又从什么时候开始，你必须主动把被推迟的约束补回来？&lt;/p&gt;&#xA;&lt;p&gt;我用三层来拆这笔账：语法层降低进入成本，运行时层把约束推迟到执行期，工程层把作者脑中的上下文换成文件、命令、测试和配置。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;三层复杂度流向总览&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch0-complexity-transfer-overview.png&#34; alt=&#34;三层复杂度流向总览&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;真正贵的是：脚本层的心理模型，被带进了工程层。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;如果时间有限&lt;/strong&gt;——第三章账单拆解把「越贵」变成数字（1 文件→8 文件）；第二章类型错误实验展示延迟结算怎么发生；第四章三层判断表可直接拿来用。只看一段，看第三章。&lt;/p&gt;&#xA;&lt;h2 id=&#34;一语法层python-把入场券做便宜了&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e8%af%ad%e6%b3%95%e5%b1%82python-%e6%8a%8a%e5%85%a5%e5%9c%ba%e5%88%b8%e5%81%9a%e4%be%bf%e5%ae%9c%e4%ba%86&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、语法层：Python 把入场券做便宜了&#xA;&lt;/h2&gt;&lt;h3 id=&#34;低摩擦不是错觉&#34;&gt;&lt;a href=&#34;#%e4%bd%8e%e6%91%a9%e6%93%a6%e4%b8%8d%e6%98%af%e9%94%99%e8%a7%89&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;低摩擦不是错觉&#xA;&lt;/h3&gt;&lt;p&gt;先把话说清楚：Python 的简单不是错觉。&lt;/p&gt;&#xA;&lt;p&gt;Python 的设计哲学里有几句话几乎被引用到烂：&lt;code&gt;Simple is better than complex.&lt;/code&gt;，&lt;code&gt;Readability counts.&lt;/code&gt;，&lt;code&gt;Explicit is better than implicit.&lt;/code&gt;。这些不是社区鸡汤，而是 Python 长期设计取舍的一部分。&lt;/p&gt;&#xA;&lt;p&gt;有意思的是，Python 的哲学里也写着 explicit is better than implicit——工程层补约束不是背叛 Python，而是在另一层兑现这句话。&lt;/p&gt;&#xA;&lt;p&gt;所以很多人第一次用 Python，会有一种&amp;quot;终于不用和语言打架&amp;quot;的感觉。你不需要显式声明变量类型，不需要为一个小脚本搭构建系统，不需要先写一堆类和入口函数。打开文件，写代码，运行。&lt;/p&gt;&#xA;&lt;p&gt;这份低摩擦让探索变快，也让很多小自动化变得值得做。数据处理、胶水代码、运维脚本、原型验证，这些任务最怕的不是代码难，而是启动成本高。&lt;/p&gt;&#xA;&lt;p&gt;所以，不能把 Python 的简单说成&amp;quot;偷懒&amp;quot;。它在语法层确实降低了进入成本。&lt;/p&gt;&#xA;&lt;h3 id=&#34;作者脑子里的四张便签&#34;&gt;&lt;a href=&#34;#%e4%bd%9c%e8%80%85%e8%84%91%e5%ad%90%e9%87%8c%e7%9a%84%e5%9b%9b%e5%bc%a0%e4%be%bf%e7%ad%be&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;作者脑子里的四张便签&#xA;&lt;/h3&gt;&lt;p&gt;但一个脚本只给自己用时，很多约束可以留在脑子里：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;我知道这个 CSV 的字段永远有 &lt;code&gt;amount&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;我知道这台机器上装了什么包。&lt;/li&gt;&#xA;&lt;li&gt;我知道这个脚本要在项目根目录运行。&lt;/li&gt;&#xA;&lt;li&gt;我知道错了就手动看一眼输出。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些知识没有消失，只是没有写进代码和工程文件里。&lt;/p&gt;&#xA;&lt;p&gt;在个人脚本阶段，这通常没问题。上下文就在作者脑子里，脚本生命周期也短。&lt;/p&gt;&#xA;&lt;p&gt;可一旦第二个人要复跑，一旦脚本要定时执行，一旦结果要进入业务流程，一旦未来有人要改它，原来那些&amp;quot;我知道&amp;quot;就会变成问题。&lt;/p&gt;&#xA;&lt;p&gt;这不是 Python 独有的问题。任何语言的脚本都会遇到它。Python 的特殊之处在于，它把第一步做得太顺了。顺到你容易低估后面那些显式约束的必要性。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;简洁脚本背后的三张隐含便签&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch1-syntax-hidden-context.png&#34; alt=&#34;简洁脚本背后的三张隐含便签&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;如果代码永远停在脚本层，这很好。一旦它被定时执行、被第二个人改、被业务依赖，这些假设就要变成文件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;二运行时层约束晚一点出现不等于不存在&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e8%bf%90%e8%a1%8c%e6%97%b6%e5%b1%82%e7%ba%a6%e6%9d%9f%e6%99%9a%e4%b8%80%e7%82%b9%e5%87%ba%e7%8e%b0%e4%b8%8d%e7%ad%89%e4%ba%8e%e4%b8%8d%e5%ad%98%e5%9c%a8&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、运行时层：约束晚一点出现，不等于不存在&#xA;&lt;/h2&gt;&lt;h3 id=&#34;类型错误什么时候才露面&#34;&gt;&lt;a href=&#34;#%e7%b1%bb%e5%9e%8b%e9%94%99%e8%af%af%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e6%89%8d%e9%9c%b2%e9%9d%a2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;类型错误什么时候才露面&#xA;&lt;/h3&gt;&lt;p&gt;Python 的动态特性让代码写起来很快。&lt;/p&gt;&#xA;&lt;p&gt;变量可以先用起来。函数参数不必一开始就被类型系统框住。字典、列表、对象之间可以快速拼装。很多小程序因此非常顺手。&lt;/p&gt;&#xA;&lt;p&gt;但这也意味着，一部分错误不会在写代码时被语言强制拦住，而是等到某个输入、某条路径、某次运行才露出来。&lt;/p&gt;&#xA;&lt;p&gt;我做了一个很小的实验。&lt;/p&gt;&#xA;&lt;p&gt;场景很普通：有一批用户记录，每条记录里有 &lt;code&gt;name&lt;/code&gt; 和 &lt;code&gt;age&lt;/code&gt;。函数要把 &lt;code&gt;name&lt;/code&gt; 做 &lt;code&gt;strip().lower()&lt;/code&gt; 处理。&lt;/p&gt;&#xA;&lt;p&gt;无类型检查路径里，前三条输入长这样：前两条 &lt;code&gt;name&lt;/code&gt; 是字符串，第三条 &lt;code&gt;name&lt;/code&gt; 是 &lt;code&gt;None&lt;/code&gt;。代码可以正常启动，前两条也能正常处理。直到第三条数据执行到 &lt;code&gt;.strip()&lt;/code&gt;，才报错。&lt;/p&gt;&#xA;&lt;p&gt;实测输出是这样：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[实测 Python 3.9.6] 无类型检查路径：前两个输入正常，第三个输入到运行时才报错&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;case 1: ada&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;case 2: grace&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;case 3: AttributeError: &amp;#39;NoneType&amp;#39; object has no attribute &amp;#39;strip&amp;#39;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这类问题在真实项目里并不少见。&lt;/p&gt;&#xA;&lt;p&gt;它不一定发生在第一条数据。它可能发生在某个分支、某种边界输入、某次线上数据形态变化之后。动态语言给了你表达自由，也把一部分边界检查推迟到了运行时。&lt;/p&gt;&#xA;&lt;p&gt;然后我给同一个结构加上 &lt;code&gt;TypedDict&lt;/code&gt;（关键定义：&lt;code&gt;class User(TypedDict): name: str; age: int&lt;/code&gt;），再用 mypy 做静态检查：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mypy type_late_error_demo.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;mypy 在运行前就给出错误：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;type_late_error_demo.py:26: error: Incompatible types (expression has type &amp;#34;None&amp;#34;, TypedDict item &amp;#34;name&amp;#34; has type &amp;#34;str&amp;#34;)  [typeddict-item]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;类型错误前移：mypy 在写代码时拦截 vs 运行时第三条数据才报错&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch2-type-error-shift.png&#34; alt=&#34;类型错误前移：mypy 在写代码时拦截 vs 运行时第三条数据才报错&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;两种结算方式&#34;&gt;&lt;a href=&#34;#%e4%b8%a4%e7%a7%8d%e7%bb%93%e7%ae%97%e6%96%b9%e5%bc%8f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;两种结算方式&#xA;&lt;/h3&gt;&lt;p&gt;这个实验不复杂，甚至有点朴素。&lt;/p&gt;&#xA;&lt;p&gt;但它刚好说明了问题：错误并没有被魔法消灭。它只是有两种结算方式。&lt;/p&gt;&#xA;&lt;p&gt;第一种，先享受动态灵活性，等输入触发时在运行时结算。第二种，提前写类型标注，引入类型检查工具，把一部分错误前移到开发期。&lt;/p&gt;&#xA;&lt;p&gt;需要说明的是：mypy 前移的是&amp;quot;代码内已经表达出来的类型矛盾&amp;quot;，外部数据（CSV、JSON、API 返回）仍需要 schema 校验和运行时检查兜住。类型标注、校验逻辑、测试，三者共同把一部分错误前移——不是靠任何单一手段。&lt;/p&gt;&#xA;&lt;p&gt;Python 本身允许你选择结算时机。&lt;/p&gt;&#xA;&lt;p&gt;小脚本不需要为了一个一次性任务背上完整类型系统。可团队项目如果继续用小脚本的纪律写，运行时就会成为事实上的类型检查器——只是它检查得晚，晚在用户输入之后，晚在测试遗漏之后，晚在线上数据走到那条分支之后。&lt;/p&gt;&#xA;&lt;h3 id=&#34;gil简化了某处限制了某处&#34;&gt;&lt;a href=&#34;#gil%e7%ae%80%e5%8c%96%e4%ba%86%e6%9f%90%e5%a4%84%e9%99%90%e5%88%b6%e4%ba%86%e6%9f%90%e5%a4%84&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;GIL：简化了某处，限制了某处&#xA;&lt;/h3&gt;&lt;p&gt;GIL 是另一种运行时层面的边界。&lt;/p&gt;&#xA;&lt;p&gt;很多人谈 GIL，会直接把它写成&amp;quot;Python 慢&amp;quot;的证据。这个说法太粗。&lt;/p&gt;&#xA;&lt;p&gt;更准确地说，GIL 是 CPython 运行时模型的一种设计权衡。官方术语表对它的描述很直接：全局解释器锁保证同一时间只有一个线程执行 Python 字节码。它简化了 CPython 对象模型的并发安全，但也限制了多线程在 CPU-bound 场景下的并行执行。&lt;/p&gt;&#xA;&lt;p&gt;注意这里的关键词：简化了某处，也限制了某处。这仍然是复杂度转移。&lt;/p&gt;&#xA;&lt;p&gt;我也跑了一个最小实验：同一个纯 Python CPU-bound 函数，执行 4 个任务，每个任务做 300 万次整数运算。对比三种方式：顺序执行、4 个线程、4 个进程。&lt;/p&gt;&#xA;&lt;p&gt;CPython 3.9.6 默认构建 / macOS 本机 / 单次取值用于展示边界，不用于基准结论。本机结果如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[实测 Python 3.9.6] CPU-bound 并发边界最小实验&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cpu_count=14, tasks=4, n_per_task=3000000&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sequential: elapsed=0.531s, speedup_vs_sequential=1.00x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;threading-4: elapsed=0.550s, speedup_vs_sequential=0.97x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;multiprocessing-4: elapsed=0.260s, speedup_vs_sequential=2.04x&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;GIL 三种并发模式实测：sequential / threading-4 / multiprocessing-4&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch2-gil-three-modes.png&#34; alt=&#34;GIL 三种并发模式实测对比&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;这个结果只代表这台机器上的观察。我不会把它写成&amp;quot;Python 多线程一定慢多少倍&amp;quot;。那是夸张。不同 Python 版本、不同任务颗粒度、不同 CPU 调度都可能改变绝对数值，但不改变本文讨论的工程边界判断。&lt;/p&gt;&#xA;&lt;p&gt;它足够说明一个边界：在这组 CPU-bound 任务里，4 个线程没有带来线性提速；4 个进程能利用更多核心，但你要引入进程模型、数据传递、启动开销和更复杂的调度方式。&lt;/p&gt;&#xA;&lt;p&gt;写出并发形式很简单，不等于多核并行自然发生。&lt;code&gt;threading&lt;/code&gt; 的 API 可以很顺手。真正进入 CPU-bound 并行时，你要理解 GIL，理解线程和进程的区别，理解任务颗粒度，理解数据如何跨进程传递。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;运行时层两条边界：类型错误前移与 CPU-bound 并发模型&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch2-runtime-boundaries.png&#34; alt=&#34;运行时层两条边界：类型错误前移与 CPU-bound 并发模型&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;PEP 703 从另一个方向说明了同一件事。&lt;/p&gt;&#xA;&lt;p&gt;让 CPython 支持可选 GIL，牵涉引用计数机制、对象生命周期管理和 C API 兼容性等底层改造。PEP 703 不是用来证明用户一定更累，而是说明：原本由运行时统一抽象的并发安全问题，一旦释放并行能力，就要在解释器实现、C 扩展兼容、部署选择上重新结算。&lt;/p&gt;&#xA;&lt;p&gt;截至本文撰写，默认 CPython 仍以 GIL 模型为主，free-threaded/no-GIL 是可选构建与生态迁移过程。这正好强化了论点：过去运行时替你藏住的一部分复杂度，一旦要释放出来，就得有另一套机制接住。&lt;/p&gt;&#xA;&lt;p&gt;运行时层的结论不是&amp;quot;动态类型不好&amp;quot;，也不是&amp;quot;GIL 很糟&amp;quot;。Python 让很多表达更轻，但轻不是免费。类型边界、并行边界、解释器实现边界，都会在项目规模变大后变得更重要。你可以不在第一天处理它们。但你不能假装它们不存在。&lt;/p&gt;&#xA;&lt;h2 id=&#34;三工程层从能跑到可交付中间都是账&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e5%b7%a5%e7%a8%8b%e5%b1%82%e4%bb%8e%e8%83%bd%e8%b7%91%e5%88%b0%e5%8f%af%e4%ba%a4%e4%bb%98%e4%b8%ad%e9%97%b4%e9%83%bd%e6%98%af%e8%b4%a6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、工程层：从能跑到可交付，中间都是账&#xA;&lt;/h2&gt;&lt;h3 id=&#34;从-1-个文件到-8-个文件&#34;&gt;&lt;a href=&#34;#%e4%bb%8e-1-%e4%b8%aa%e6%96%87%e4%bb%b6%e5%88%b0-8-%e4%b8%aa%e6%96%87%e4%bb%b6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;从 1 个文件到 8 个文件&#xA;&lt;/h3&gt;&lt;p&gt;如果说运行时层的边界还偏机制，那么工程层就很直观。&lt;/p&gt;&#xA;&lt;p&gt;我做了一个最小项目实验。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;4 阶段成长时间线：单文件 → 可重复 → 可测试 → 可交付&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch3-four-stages-timeline.png&#34; alt=&#34;4 阶段成长时间线：单文件 → 可重复 → 可测试 → 可交付&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;起点是一个单文件脚本：读取 &lt;code&gt;invoices.csv&lt;/code&gt;，把 &lt;code&gt;amount&lt;/code&gt; 字段加总，然后打印结果。这个脚本只有一个文件，一个命令。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python invoice_summary.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;到这里，它已经&amp;quot;能跑&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;但&amp;quot;能跑&amp;quot;不是&amp;quot;可交付&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;我把它分成四个阶段，逐步从个人脚本推进到最小可交付项目，统计文件数、命令数、配置项数和交付检查项数。计数口径很朴素：不看代码行数，不制造复杂框架，只统计为了让别人复跑、测试、类型检查和 CI 检查所需的显式约束。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;表 1：文件 ×8、命令 ×5、配置从 0 到 6——脚本到可交付的全部账&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;阶段&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;文件数&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;命令数&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;配置项数&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;交付检查项数&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;00-single-script&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;0&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;01-repeatable-script&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;3&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;2&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;3&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;02-testable-project&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;5&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;3&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;3&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;4&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;03-deliverable-project&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;8&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;5&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;6&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;6&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;从单文件脚本到最小可交付项目，文件数从 1 增加到 8，显式命令从 1 增加到 5，配置项从 0 增加到 6。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;口径明细&lt;/strong&gt;（03 阶段）：8 个文件大致组成——&lt;code&gt;pyproject.toml&lt;/code&gt; / &lt;code&gt;src/&lt;/code&gt; 入口模块 / &lt;code&gt;src/__init__.py&lt;/code&gt; / &lt;code&gt;tests/&lt;/code&gt; 测试文件 / &lt;code&gt;Makefile&lt;/code&gt; / &lt;code&gt;.github/workflows/ci.yml&lt;/code&gt; / &lt;code&gt;mypy.ini&lt;/code&gt; / &lt;code&gt;README.md&lt;/code&gt;。5 个命令——&lt;code&gt;make install&lt;/code&gt;（安装依赖）/ &lt;code&gt;make test&lt;/code&gt;（跑测试）/ &lt;code&gt;make typecheck&lt;/code&gt;（mypy 检查）/ &lt;code&gt;make smoke&lt;/code&gt;（烟测）/ &lt;code&gt;make ci&lt;/code&gt;（全流程）。6 个配置项——依赖声明 / Python 版本约束 / 测试入口 / mypy 严格度等级 / Makefile 目标定义 / CI 触发条件。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;显式约束增长曲线&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch3-project-cost-bill.png&#34; alt=&#34;显式约束增长曲线&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;这不是一个大型项目。它没有数据库，没有 Web 框架，没有外部 API，没有容器化。它只是把一个单文件脚本变成别人能复跑、能测试、能类型检查、能在 CI 里跑的最小形态。即便如此，约束已经出现了。&lt;/p&gt;&#xA;&lt;h3 id=&#34;第二个人接手时问的第一个问题&#34;&gt;&lt;a href=&#34;#%e7%ac%ac%e4%ba%8c%e4%b8%aa%e4%ba%ba%e6%8e%a5%e6%89%8b%e6%97%b6%e9%97%ae%e7%9a%84%e7%ac%ac%e4%b8%80%e4%b8%aa%e9%97%ae%e9%a2%98&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;第二个人接手时问的第一个问题&#xA;&lt;/h3&gt;&lt;p&gt;想象这样一个画面：第二个人接手脚本时不会先问语法。他第一句通常是——用哪个 Python 版本？输入路径写死了吗？我改完怎么知道没破坏旧逻辑？&lt;/p&gt;&#xA;&lt;p&gt;第一阶段，只有脚本。&lt;/p&gt;&#xA;&lt;p&gt;第二阶段，你开始补 README 和依赖口径。哪怕依赖只有标准库，你也要告诉别人&amp;quot;当前没有第三方依赖&amp;quot;。否则别人不知道是忘了写，还是确实没有。&lt;/p&gt;&#xA;&lt;p&gt;Python 依赖管理不是&amp;quot;有没有第三方包&amp;quot;这么简单。它还包括 venv 隔离（全局还是项目级）、依赖文件格式选择（requirements.txt 还是 pyproject.toml）、lockfile 是否启用、构建后端选哪一个（setuptools / hatchling / poetry-core 等）。每一项都是原来&amp;quot;作者本来知道&amp;quot;的隐性选择被显式化的过程。这里不是在推荐工具——而是在说明：选择动作本身就是工程成本。&lt;/p&gt;&#xA;&lt;p&gt;第三阶段，你把逻辑挪到 &lt;code&gt;src/&lt;/code&gt;，加测试。因为只要别人要改它，测试就不再是装饰，而是协作边界。&lt;/p&gt;&#xA;&lt;p&gt;第四阶段，你补 &lt;code&gt;pyproject.toml&lt;/code&gt;、mypy 配置、Makefile、CI workflow、模块入口。因为项目一旦要交付，就不能只依赖&amp;quot;作者知道怎么跑&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;我还对最终阶段做了一次烟测：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Ran 1 test in 0.001s&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OK&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Success: no issues found in 3 source files&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这说明这个最小可交付项目不是纸面清单。最小烟测能跑，类型检查能过——它只是一个最低门槛，不是完整质量保证。&lt;/p&gt;&#xA;&lt;p&gt;代价也在这里变得很具体。&lt;/p&gt;&#xA;&lt;p&gt;从&amp;quot;作者本地执行成功&amp;quot;到&amp;quot;别人能理解、能复跑、能修改、能检查&amp;quot;，你必须把这些问题写清楚：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;入口在哪里？&lt;/li&gt;&#xA;&lt;li&gt;包结构是什么？&lt;/li&gt;&#xA;&lt;li&gt;依赖怎么安装？&lt;/li&gt;&#xA;&lt;li&gt;测试怎么跑？&lt;/li&gt;&#xA;&lt;li&gt;类型检查怎么跑？&lt;/li&gt;&#xA;&lt;li&gt;常用命令是否固定？&lt;/li&gt;&#xA;&lt;li&gt;CI 是否能复跑？&lt;/li&gt;&#xA;&lt;li&gt;README 是否足够让非作者接手？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些问题不是 Python 制造的。任何语言做工程都要回答。&lt;/p&gt;&#xA;&lt;p&gt;但 Python 的脚本体验太顺滑，所以这些问题更容易被推迟。你会先得到一个看似完成的东西，然后在交付边界上再把它们一项项补回来。&lt;/p&gt;&#xA;&lt;p&gt;贵的不是语法。贵的是把隐性上下文变成显式约束——作者上下文、运行环境、正确性检查，每一项都要从脑子里搬进文件。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;隐性上下文转移到工程文件&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch3-context-to-files.png&#34; alt=&#34;隐性上下文转移到工程文件&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;个人脚本阶段，这些上下文可以不写。&lt;/p&gt;&#xA;&lt;p&gt;团队复用阶段，它们不写就会变成沟通成本。&lt;/p&gt;&#xA;&lt;p&gt;长期交付阶段，它们不写就会变成风险。&lt;/p&gt;&#xA;&lt;p&gt;一开始，脚本作者只需要知道：&lt;code&gt;python invoice_summary.py&lt;/code&gt; 能跑。第二个人接手时，他会问：用哪个 Python 版本？输入路径能不能改？&lt;code&gt;amount&lt;/code&gt; 为空怎么办？改完怎么知道没破坏旧逻辑？以后手动跑、定时跑，还是放进 CI？&lt;/p&gt;&#xA;&lt;p&gt;这些问题不是挑刺。它们只是把原来藏在作者使用习惯里的信息，搬到了协作边界上。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;表 2：&amp;ldquo;作者本来知道&amp;quot;的 5 条上下文分别搬到了哪里&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;原位置&lt;/th&gt;&#xA;          &lt;th&gt;转移到&lt;/th&gt;&#xA;          &lt;th&gt;应补&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;作者知道输入文件叫什么&lt;/td&gt;&#xA;          &lt;td&gt;README / 参数 / 配置&lt;/td&gt;&#xA;          &lt;td&gt;运行说明、参数边界&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;作者知道机器上有什么依赖&lt;/td&gt;&#xA;          &lt;td&gt;venv / requirements / pyproject&lt;/td&gt;&#xA;          &lt;td&gt;依赖声明、版本约束&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;作者手动看输出对不对&lt;/td&gt;&#xA;          &lt;td&gt;tests&lt;/td&gt;&#xA;          &lt;td&gt;自动测试&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;作者知道哪些输入不合法&lt;/td&gt;&#xA;          &lt;td&gt;类型标注 / 校验逻辑&lt;/td&gt;&#xA;          &lt;td&gt;输入模型、异常处理&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;作者本地手动运行&lt;/td&gt;&#xA;          &lt;td&gt;Makefile / CI&lt;/td&gt;&#xA;          &lt;td&gt;可复跑命令、流水线&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;知道自己在哪一层&#34;&gt;&lt;a href=&#34;#%e7%9f%a5%e9%81%93%e8%87%aa%e5%b7%b1%e5%9c%a8%e5%93%aa%e4%b8%80%e5%b1%82&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;知道自己在哪一层&#xA;&lt;/h3&gt;&lt;p&gt;这里有一个很容易误解的点。&lt;/p&gt;&#xA;&lt;p&gt;我不是说 Python 项目必须一开始就上完整工程模板。那会把 Python 最有价值的低摩擦优势毁掉。&lt;/p&gt;&#xA;&lt;p&gt;一个一次性脚本不需要 CI。一个本地数据清洗脚本不一定需要 &lt;code&gt;pyproject.toml&lt;/code&gt;。一个探索性 notebook 不需要被包装成服务。&lt;/p&gt;&#xA;&lt;p&gt;真正的问题是：你要知道自己在哪一层。&lt;/p&gt;&#xA;&lt;p&gt;如果你在脚本层，就享受简单。如果你进入工程层，就补约束。最糟糕的状态，是项目已经进入工程层，团队还假装它只是脚本。&lt;/p&gt;&#xA;&lt;h2 id=&#34;四边界层什么时候享受简单什么时候补约束&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e8%be%b9%e7%95%8c%e5%b1%82%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e4%ba%ab%e5%8f%97%e7%ae%80%e5%8d%95%e4%bb%80%e4%b9%88%e6%97%b6%e5%80%99%e8%a1%a5%e7%ba%a6%e6%9d%9f&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、边界层：什么时候享受简单，什么时候补约束&#xA;&lt;/h2&gt;&lt;h3 id=&#34;三层三种成本分布&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e5%b1%82%e4%b8%89%e7%a7%8d%e6%88%90%e6%9c%ac%e5%88%86%e5%b8%83&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三层，三种成本分布&#xA;&lt;/h3&gt;&lt;p&gt;Python 的争议经常被写成语言优劣判断。这会让讨论变钝。&lt;/p&gt;&#xA;&lt;p&gt;更有用的问题不是&amp;quot;Python 好不好&amp;rdquo;，而是&amp;quot;这段 Python 代码正在消费哪一层的简单&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;我通常先看三层。&lt;/p&gt;&#xA;&lt;p&gt;第一层，脚本层。这里的目标是快。探索一个想法，处理一批数据，写一个内部小工具，自动化一个重复动作。代码生命周期短，协作人数少，失败影响可控。在这一层，Python 的简单几乎就是净收益。你不必为了一个下午就能丢掉的脚本建立完整项目结构。&lt;/p&gt;&#xA;&lt;p&gt;第二层，服务层或团队复用层。代码开始被别人调用、修改、部署。输入不再只来自作者手工准备。错误影响不再只停留在本地。这个时候，Python 的简单仍然有价值，但你要开始补边界。至少补四件事：输入校验、测试、依赖声明、运行说明。如果代码有稳定接口，再补类型标注和类型检查。&lt;/p&gt;&#xA;&lt;p&gt;第三层，系统层。代码进入长期维护，有明确 SLA，或者成为业务链路的一部分。这里就不能只靠&amp;quot;Python 写起来快&amp;quot;。你要考虑并发模型、部署边界、可观测性、回滚、CI、版本锁定、性能瓶颈和团队规范。在这一层，简单仍然重要，但它只是让你更快写出第一版。后面的约束，仍然要用工程纪律补回来。&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;表 3：同一门语言，三层代码该补的约束完全不同&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;代码层级&lt;/th&gt;&#xA;          &lt;th&gt;简单的主要收益&lt;/th&gt;&#xA;          &lt;th&gt;最容易被推迟的&lt;/th&gt;&#xA;          &lt;th&gt;应补&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;脚本层&lt;/td&gt;&#xA;          &lt;td&gt;快速表达、低摩擦试错&lt;/td&gt;&#xA;          &lt;td&gt;可复跑说明、输入边界&lt;/td&gt;&#xA;          &lt;td&gt;简短 README、参数说明&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;团队复用层&lt;/td&gt;&#xA;          &lt;td&gt;低成本迭代、容易改&lt;/td&gt;&#xA;          &lt;td&gt;类型错误、测试缺口、依赖漂移&lt;/td&gt;&#xA;          &lt;td&gt;测试、类型标注、依赖声明、运行命令&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;服务/系统层&lt;/td&gt;&#xA;          &lt;td&gt;业务代码表达效率高&lt;/td&gt;&#xA;          &lt;td&gt;并发边界、部署风险、长期维护&lt;/td&gt;&#xA;          &lt;td&gt;CI、监控、版本锁、架构边界、性能验证&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;三层决策卡&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch4-decision-cards.png&#34; alt=&#34;三层决策卡&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;这张表背后只有一个判断：复杂度不按语言宣传语结算，它按系统责任结算。&lt;/p&gt;&#xA;&lt;p&gt;你负责的东西越少，Python 的简单越接近纯收益。你负责的东西越多，越要把简单背后的隐性假设写出来。&lt;/p&gt;&#xA;&lt;h3 id=&#34;争论为什么总是鸡同鸭讲&#34;&gt;&lt;a href=&#34;#%e4%ba%89%e8%ae%ba%e4%b8%ba%e4%bb%80%e4%b9%88%e6%80%bb%e6%98%af%e9%b8%a1%e5%90%8c%e9%b8%ad%e8%ae%b2&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;争论为什么总是鸡同鸭讲&#xA;&lt;/h3&gt;&lt;p&gt;这也是为什么同一个人会同时说两句话：&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;Python 写脚本真的爽。&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;Python 项目大了以后要很强的工程纪律。&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;第一句话说的是语法层和探索层。第二句话说的是工程层和交付层。争论通常发生在这里：一个人拿脚本层体验证明 Python 简单，另一个人拿工程层痛苦反驳 Python 复杂。两个人都没错，只是约束发生在不同楼层。&lt;/p&gt;&#xA;&lt;p&gt;别用同一把尺子量所有 Python 代码。一个临时脚本，不必被服务化标准绑架。一个长期服务，也不能拿临时脚本的自由当借口。&lt;/p&gt;&#xA;&lt;h2 id=&#34;结尾把简单用在它最值钱的位置&#34;&gt;&lt;a href=&#34;#%e7%bb%93%e5%b0%be%e6%8a%8a%e7%ae%80%e5%8d%95%e7%94%a8%e5%9c%a8%e5%ae%83%e6%9c%80%e5%80%bc%e9%92%b1%e7%9a%84%e4%bd%8d%e7%bd%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;结尾：把简单用在它最值钱的位置&#xA;&lt;/h2&gt;&lt;p&gt;Python 的简单当然值得用。&lt;/p&gt;&#xA;&lt;p&gt;很多自动化任务，本来就不值得先搭一套工程架子。先把问题跑通，先看见结果，先验证方向，这正是 Python 最舒服的地方。&lt;/p&gt;&#xA;&lt;p&gt;但简单不是免维护承诺。前面少付一点，后面在正确的地方补回来。补得早，是工程纪律；补得晚，就会变成线上排障、协作摩擦和交付风险。&lt;/p&gt;&#xA;&lt;p&gt;这四问不是凭感觉打分，而是在判断哪些隐性上下文必须从脑子里搬进 README、tests、pyproject、Makefile、CI——也就是第三章那张账单要补哪几行。&lt;/p&gt;&#xA;&lt;p&gt;如果你要判断一段 Python 代码该补多少约束，可以先问四个问题：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;四个判断问题&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/python-simplicity-cost/ch5-four-question-checklist.png&#34; alt=&#34;四个判断问题&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;这段代码只有我一个人运行吗？&lt;/li&gt;&#xA;&lt;li&gt;输入是否稳定、可控、可预测？&lt;/li&gt;&#xA;&lt;li&gt;出错影响是否只停留在本地？&lt;/li&gt;&#xA;&lt;li&gt;三个月后别人是否需要接手？&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;如果四个答案都偏&amp;quot;是&amp;quot;，享受 Python 的简单。&lt;/p&gt;&#xA;&lt;p&gt;如果其中任意两项答案开始模糊，就别再假装它只是脚本。先补测试、依赖声明、运行说明和输入边界。只要影响线上、财务、数据安全或多人复用，哪怕只有一项变成否，就该补测试、回滚或监控——不必等到两个否。&lt;/p&gt;&#xA;&lt;p&gt;如果它已经进入服务或系统层，再把类型检查、CI、版本约束、可观测性和并发边界纳入设计。&lt;/p&gt;&#xA;&lt;p&gt;Python 让第一步更便宜。&lt;/p&gt;&#xA;&lt;p&gt;工程要做的，是别拿第一步的便宜，去抵扣后面所有账单。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;strong&gt;参考来源 / 延伸阅读&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;PEP 20 — The Zen of Python：https://peps.python.org/pep-0020/&lt;/li&gt;&#xA;&lt;li&gt;Python 官方术语表 — Global Interpreter Lock：https://docs.python.org/3/glossary.html#term-global-interpreter-lock&lt;/li&gt;&#xA;&lt;li&gt;PEP 703 — Making the Global Interpreter Lock Optional in CPython：https://peps.python.org/pep-0703/&lt;/li&gt;&#xA;&lt;li&gt;mypy 文档 — TypedDict：https://mypy.readthedocs.io/en/stable/typed_dict.html&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;原文发布于 &lt;a class=&#34;link&#34; href=&#34;https://www.wujiachen.com.cn/posts/python-simplicity-cost&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;止语Lab&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;</description>
        </item></channel>
</rss>
