<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.wenhao.ink/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.wenhao.ink/" rel="alternate" type="text/html" /><updated>2026-09-07T10:54:53+08:00</updated><id>https://www.wenhao.ink/feed.xml</id><title type="html">wenhao</title><subtitle>A (nearly) no-CSS, fast, minimalist Jekyll theme.
</subtitle><author><name>wenhao</name></author><entry><title type="html">理解 Cloudflare Workers</title><link href="https://www.wenhao.ink/cloudflare-workers-20260907/" rel="alternate" type="text/html" title="理解 Cloudflare Workers" /><published>2026-09-07T00:00:00+08:00</published><updated>2026-09-07T00:00:00+08:00</updated><id>https://www.wenhao.ink/cloudflare-workers</id><content type="html" xml:base="https://www.wenhao.ink/cloudflare-workers-20260907/"><![CDATA[<p>前面了解 Cloudflare 的一些能力时，看到 Workers 总觉得它像一台部署在 Cloudflare 上的 Node.js 服务器。真正动手创建一个 Worker 后，才发现它和传统服务并不是一回事。</p>

<p>Worker 不是一台服务器，也不是一个需要自己常驻运行的进程。它更像是部署在 Cloudflare 网络中的一段事件处理代码：请求来了，Cloudflare 调用它；它处理完请求，返回结果。</p>

<p>最常见的事件就是 HTTP 请求。一个最小的 Worker 大致是这样：</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="k">async</span> <span class="nx">fetch</span><span class="p">(</span><span class="na">request</span><span class="p">:</span> <span class="nx">Request</span><span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">Response</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello Worker!</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>

<p>这里没有 <code class="language-plaintext highlighter-rouge">app.listen()</code>，也没有端口号。我们只需要实现 <code class="language-plaintext highlighter-rouge">fetch</code>，请求到来时 Cloudflare 会把请求交给它，并把返回的 <code class="language-plaintext highlighter-rouge">Response</code> 发回给用户。</p>

<h2 id="如何开始使用">如何开始使用</h2>

<p>Cloudflare 提供了 Wrangler 作为本地开发和部署工具。创建项目、在本地运行、部署到线上，通常只需要下面几步：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm create cloudflare@latest <span class="nt">--</span> hello-worker
<span class="nb">cd </span>hello-worker
npm run dev
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">npm run dev</code> 会在本地启动开发环境。修改 <code class="language-plaintext highlighter-rouge">src/index.ts</code> 后，访问本地地址就可以看到结果。确认没有问题后，再执行：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run deploy
</code></pre></div></div>

<p>Wrangler 会将项目中实际需要的代码和配置部署到 Cloudflare。部署完成后，Worker 可以绑定到 <code class="language-plaintext highlighter-rouge">workers.dev</code> 地址、自定义域名，或者站点的某个路由。</p>

<h2 id="一个请求是怎样被处理的">一个请求是怎样被处理的</h2>

<p>用户访问 Worker 的地址时，请求会先到达 Cloudflare 的网络。Cloudflare 根据域名和路由找到对应的 Worker，再调用它的 <code class="language-plaintext highlighter-rouge">fetch</code> 方法。</p>

<p>在 <code class="language-plaintext highlighter-rouge">fetch</code> 中，可以直接读取 URL、请求头和请求体，也可以用 <code class="language-plaintext highlighter-rouge">fetch</code> 请求其他服务。例如可以在这里做鉴权、改写请求、读取数据，或者把请求转发到已有的后端服务。最后返回一个 <code class="language-plaintext highlighter-rouge">Response</code>，这一次请求就结束了。</p>

<p>所以 Worker 很适合放在请求入口处。它可以是一个简单的 API，也可以做页面渲染、接口聚合、鉴权、缓存控制，或者在请求到源站之前先处理一遍。</p>

<p>除了 HTTP 请求，Worker 也可以处理定时任务、队列消息等事件。不过理解 HTTP 请求的处理方式后，其他事件的思路也是一样的：Cloudflare 在事件发生时调用代码，而不是让我们维护一个一直等待事件的服务进程。</p>

<h2 id="serverless-到底是什么">Serverless 到底是什么</h2>

<p>Serverless 并不是没有服务器。服务器依然存在，只是由 Cloudflare 负责运行、扩容、维护和调度。</p>

<p>传统部署时，我们通常需要关心一台机器或一个容器：进程是否启动、监听哪个端口、流量增加时开多少实例、实例异常后如何重启。Worker 把这些事情收进了平台内部。开发者只关注收到什么事件，以及应该返回什么结果。</p>

<p>这也是 Serverless 最重要的变化：从“维护一个长期运行的应用”，变成“描述一次事件该如何处理”。</p>

<p>Cloudflare Workers 使用 V8 isolate 运行代码。它比启动一个完整的 Node.js 进程轻得多，因此可以更快地创建和复用运行环境。但这不表示某个 Worker 会一直留在内存中。下一次请求可能复用原来的 isolate，也可能被分配到新的 isolate。</p>

<p>因此不能把 Worker 的内存当成数据库。例如下面这样的全局变量，不能用于保存用户数据或业务状态：</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div></div>

<p>它可能因为运行环境被复用而暂时保留，也可能在下一次请求时消失。全局变量可以用作临时优化，但不能作为正确性所依赖的数据来源。</p>

<h2 id="计算和状态分开">计算和状态分开</h2>

<p>这就是 Serverless 开发中很重要的一点：把计算和状态分开。</p>

<p>Worker 负责处理请求和执行业务逻辑；需要持久化的数据，则放到专门的存储中。比如 D1 适合关系型数据，KV 适合配置和读取频繁的键值数据，R2 用来放文件和对象。如果业务需要对某个对象进行有状态的协调，还可以使用 Durable Objects。</p>

<p>这样做一开始会觉得比把数据放在内存里麻烦，但它让服务可以随时扩容、迁移和恢复，而不依赖某个特定进程一直活着。</p>

<p>理解 Worker 的关键不在于它用了什么打包工具，而在于转换这个思路：它不是一台缩小版的服务器，而是一段由平台按事件调用的代码。把状态交给合适的存储，把请求处理交给 Worker，才是使用 Serverless 的正确方式。</p>

<h3 id="资料">资料</h3>

<ul>
  <li><a href="https://developers.cloudflare.com/workers/get-started/guide/">Cloudflare Workers 快速开始</a></li>
  <li><a href="https://developers.cloudflare.com/workers/reference/how-workers-works/">Cloudflare Workers 的运行方式</a></li>
</ul>]]></content><author><name>Hao</name></author><category term="programme" /><category term="cloudflare" /><category term="workers" /><category term="serverless" /><category term="javascript" /><summary type="html"><![CDATA[前面了解 Cloudflare 的一些能力时，看到 Workers 总觉得它像一台部署在 Cloudflare 上的 Node.js 服务器。真正动手创建一个 Worker 后，才发现它和传统服务并不是一回事。]]></summary></entry><entry><title type="html">也就是一个 yield：从 FastAPI 到协程底层</title><link href="https://www.wenhao.ink/python-yield-fastapi-coroutine-20260902/" rel="alternate" type="text/html" title="也就是一个 yield：从 FastAPI 到协程底层" /><published>2026-09-02T00:00:00+08:00</published><updated>2026-09-02T00:00:00+08:00</updated><id>https://www.wenhao.ink/python-yield-fastapi-coroutine</id><content type="html" xml:base="https://www.wenhao.ink/python-yield-fastapi-coroutine-20260902/"><![CDATA[<p>如果你最近在使用 FastAPI，你可能见过下面这段代码。它是官方推荐用来管理应用生命周期（比如数据库连接与断开）的标准写法：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">asynccontextmanager</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">lifespan</span><span class="p">(</span><span class="n">app</span><span class="p">:</span> <span class="n">FastAPI</span><span class="p">):</span>
    <span class="c1"># 1. 启动前：连接资源
</span>    <span class="k">await</span> <span class="n">redis_service</span><span class="p">.</span><span class="n">connect</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"&gt;&gt;&gt; 服务已启动"</span><span class="p">)</span>
    
    <span class="k">yield</span>  <span class="c1"># &lt;--- 关键看这里
</span>    
    <span class="c1"># 2. 关闭后：清理资源
</span>    <span class="k">print</span><span class="p">(</span><span class="s">"&gt;&gt;&gt; 服务正在关闭"</span><span class="p">)</span>
    <span class="k">await</span> <span class="n">redis_service</span><span class="p">.</span><span class="n">disconnect</span><span class="p">()</span>
</code></pre></div></div>

<p>这段代码看着简单，但却引出了 Python 中最迷人、也是最重要的机制之一：<strong>生成器（Generator）</strong>。</p>

<p>为什么一个 <code class="language-plaintext highlighter-rouge">yield</code> 就能让函数“暂停”？它会不会阻塞线程？Java 和 JavaScript 也有类似机制吗？本文将从应用层深入到 CPython 虚拟机底层，带你彻底理解 <code class="language-plaintext highlighter-rouge">yield</code> 背后的黑魔法。</p>

<hr />

<h2 id="一-什么是生成器从返回到产出">一、 什么是生成器：从“返回”到“产出”</h2>

<p>普通函数是“一锤子买卖”，调用 <code class="language-plaintext highlighter-rouge">return</code> 后，函数结束，所有局部变量销毁。
而生成器函数（带 <code class="language-plaintext highlighter-rouge">yield</code> 的函数）是<strong>“可以暂停的函数”</strong>。</p>

<p>形象的理解：</p>
<ul>
  <li><strong>普通函数（List）</strong>：就像去超市买薯片，直接给你一整箱，你得有力气扛回家（内存占用大）。</li>
  <li><strong>生成器（Yield）</strong>：就像自动售货机。你需要一包，它“吐”出来一包。它<strong>按需生产</strong>，不占内存。</li>
</ul>

<p>在 FastAPI 的 <code class="language-plaintext highlighter-rouge">lifespan</code> 中，<code class="language-plaintext highlighter-rouge">yield</code> 起到的作用不是生成数据，而是<strong>控制流的移交</strong>：</p>
<ol>
  <li>运行到 <code class="language-plaintext highlighter-rouge">yield</code>，函数暂停（挂起），控制权交给 FastAPI 主程序去处理 HTTP 请求。</li>
  <li>当应用关闭时，FastAPI 像按了“继续播放”键一样，让函数从 <code class="language-plaintext highlighter-rouge">yield</code> 后面继续运行，执行清理工作。</li>
</ol>

<hr />

<h2 id="二-核心误区yield-会挂起线程吗">二、 核心误区：<code class="language-plaintext highlighter-rouge">yield</code> 会挂起线程吗？</h2>

<p>这是很多开发者的误区。答案是：<strong>绝对不会。</strong></p>

<p><strong><code class="language-plaintext highlighter-rouge">yield</code> 挂起的是“函数”，而不是“线程”。</strong></p>

<p>我们可以通过打印线程 ID 来验证：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">threading</span>

<span class="k">def</span> <span class="nf">my_generator</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"生成器内 线程ID: </span><span class="si">{</span><span class="n">threading</span><span class="p">.</span><span class="n">get_ident</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="k">yield</span> <span class="mi">1</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"生成器内 线程ID: </span><span class="si">{</span><span class="n">threading</span><span class="p">.</span><span class="n">get_ident</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"主程序   线程ID: </span><span class="si">{</span><span class="n">threading</span><span class="p">.</span><span class="n">get_ident</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="n">gen</span> <span class="o">=</span> <span class="n">my_generator</span><span class="p">()</span>
    <span class="nb">next</span><span class="p">(</span><span class="n">gen</span><span class="p">)</span>
    <span class="k">print</span><span class="p">(</span><span class="s">"--- 主程序继续干活 ---"</span><span class="p">)</span>
    <span class="nb">next</span><span class="p">(</span><span class="n">gen</span><span class="p">)</span>

<span class="c1"># 输出结果显示，所有 ID 都是一模一样的！
</span></code></pre></div></div>

<p><strong>发生了什么？</strong>
这就像一场<strong>接力赛</strong>。</p>
<ul>
  <li>生成器跑了一段，通过 <code class="language-plaintext highlighter-rouge">yield</code> 把棒子（CPU 控制权）交还给调用者。</li>
  <li>调用者跑了一段，通过 <code class="language-plaintext highlighter-rouge">next()</code> 把棒子又交回给生成器。</li>
  <li><strong>赛道上（线程）始终有人在跑，CPU 从未停歇。</strong></li>
</ul>

<p>相比之下，<code class="language-plaintext highlighter-rouge">time.sleep()</code> 才是真正的线程挂起，它会让操作系统把当前线程踢出 CPU，导致 CPU 闲置或调度其他线程。</p>

<hr />

<h2 id="三-底层原理python-如何实现冻结时间">三、 底层原理：Python 如何实现“冻结”时间？</h2>

<p>为什么 C/C++ 的函数一旦 return 栈内存就没了，而 Python 可以“复活”？</p>

<p>这得益于 Python 的<strong>解释器架构</strong>。</p>

<h3 id="1-栈帧对象stack-frame-object">1. 栈帧对象（Stack Frame Object）</h3>
<p>在 C 语言层面，函数调用栈是操作系统分配的一块连续内存，函数返回即释放，不可逆。
而在 Python 中，<strong>栈帧（Stack Frame）是一个在“堆内存”上分配的对象（<code class="language-plaintext highlighter-rouge">PyFrameObject</code>）。</strong></p>

<h3 id="2-堆上的魔法">2. 堆上的魔法</h3>
<p>既然是堆上的对象，它的生命周期就不受系统栈的限制，而是由 Python 解释器控制。</p>

<p>当你调用生成器时：</p>
<ol>
  <li><strong>冻结（Yield）</strong>：解释器暂停执行，保存当前的指令指针（<code class="language-plaintext highlighter-rouge">f_lasti</code>，记录代码跑到哪一行），并将这个<strong>栈帧对象</strong>从执行链上摘下来，保存在生成器对象里。<strong>栈帧没有被销毁，局部变量依然活着。</strong></li>
  <li><strong>恢复（Next）</strong>：解释器取出这个栈帧对象，重新挂回执行链，根据指令指针跳转到 <code class="language-plaintext highlighter-rouge">yield</code> 的下一行继续执行。</li>
</ol>

<p>这完全是<strong>用户态（User Mode）</strong>的行为，操作系统对此一无所知。</p>

<hr />

<h2 id="四-进阶从-yield-到-asyncawait">四、 进阶：从 <code class="language-plaintext highlighter-rouge">yield</code> 到 <code class="language-plaintext highlighter-rouge">async/await</code></h2>

<p><code class="language-plaintext highlighter-rouge">yield</code> 的功能经历了三个阶段的演变，最终成就了今天的 Python 异步生态：</p>

<ol>
  <li><strong>迭代器时代</strong>：单纯为了省内存，生成斐波那契数列等。</li>
  <li><strong>协程萌芽</strong>：Python 2.5 引入 <code class="language-plaintext highlighter-rouge">send()</code>，允许外部向生成器发送数据。生成器变成了<strong>协程（Coroutine）</strong>，可以实现协作式多任务。</li>
  <li><strong>原生协程</strong>：Python 3.5+ 引入 <code class="language-plaintext highlighter-rouge">async/await</code>。虽然语法变了，但底层依然是基于“函数暂停/恢复”的机制。</li>
</ol>

<p>FastAPI之所以快，就是因为它利用这种机制（基于 <code class="language-plaintext highlighter-rouge">asyncio</code>），在单线程内实现了高并发。当一个请求在等待数据库（IO）时，Python 利用 <code class="language-plaintext highlighter-rouge">await</code>（底层类似 yield）挂起当前任务，立刻切换去处理另一个请求，压榨 CPU 的每一分性能。</p>

<hr />

<h2 id="五-横向对比java-和-javascript-怎么做">五、 横向对比：Java 和 JavaScript 怎么做？</h2>

<p>Python 这种“解释器保留堆栈帧”的做法并非业界唯一标准。</p>

<h3 id="1-java">1. Java</h3>
<p>Java 语言层面<strong>没有</strong>像 Python 这样通用的 <code class="language-plaintext highlighter-rouge">yield</code> 生成器。</p>
<ul>
  <li><strong>做数据流</strong>：使用 <code class="language-plaintext highlighter-rouge">Stream.iterate</code>，基于函数式编程接口。</li>
  <li><strong>做并发</strong>：Java 长期依赖操作系统线程（Thread）。直到 Java 21 推出的 <strong>Project Loom (Virtual Threads)</strong>，才在 JVM 层面实现了轻量级挂起。但它不需要程序员手动写 <code class="language-plaintext highlighter-rouge">yield</code>，JVM 会自动检测阻塞并挂起虚拟线程。</li>
</ul>

<h3 id="2-javascript">2. JavaScript</h3>
<p>JS 的实现与 Python 类似但不同：</p>
<ul>
  <li><strong>机制</strong>：<code class="language-plaintext highlighter-rouge">function*</code> / <code class="language-plaintext highlighter-rouge">yield</code> 和 <code class="language-plaintext highlighter-rouge">async/await</code>。</li>
  <li><strong>实现</strong>：V8 引擎通常会将生成器编译成一个<strong>状态机（State Machine）</strong>。它不是像 Python 那样保留整个栈帧对象，而是把函数代码切片，利用<strong>闭包</strong>保存变量，通过 <code class="language-plaintext highlighter-rouge">switch-case</code> 跳转执行位置。</li>
  <li><strong>调度</strong>：JS 强依赖<strong>事件循环（Event Loop）</strong>和<strong>Promise</strong>。<code class="language-plaintext highlighter-rouge">await</code> 本质上是将后续代码封装成回调放入微任务队列。</li>
</ul>

<hr />

<h2 id="总结">总结</h2>

<p>当你再次在 FastAPI 中写下 <code class="language-plaintext highlighter-rouge">yield</code>，或者在脚本中写下 <code class="language-plaintext highlighter-rouge">async def</code> 时，请记住：</p>

<p>你正在使用 Python 最强大的特性之一：<strong>用户态非抢占式多任务处理</strong>。</p>

<p>它利用<strong>堆内存上的栈帧对象</strong>，骗过了操作系统，实现了代码执行流的自由穿梭。这不仅让代码更优雅，更是 Python 在高并发领域的一把利剑。</p>]]></content><author><name>Hao</name></author><category term="programme" /><category term="python" /><category term="fastapi" /><category term="coroutine" /><summary type="html"><![CDATA[如果你最近在使用 FastAPI，你可能见过下面这段代码。它是官方推荐用来管理应用生命周期（比如数据库连接与断开）的标准写法：]]></summary></entry><entry><title type="html">觉醒的算法：用概率论与系统论重构你的生命底座</title><link href="https://www.wenhao.ink/awakening-algorithm-20260706/" rel="alternate" type="text/html" title="觉醒的算法：用概率论与系统论重构你的生命底座" /><published>2026-07-06T00:00:00+08:00</published><updated>2026-07-06T00:00:00+08:00</updated><id>https://www.wenhao.ink/awakening-algorithm</id><content type="html" xml:base="https://www.wenhao.ink/awakening-algorithm-20260706/"><![CDATA[<p>在这个充满波动、裁员、AI冲击和不确定性的时代，人类对“确定性”的渴求正达到前所未有的峰值。然而，这种渴求往往是痛苦的源泉。</p>

<p>最近读到一篇深度好文，它提出了一个令人耳目一新的视角：<strong>佛学，本质上是一套“高维不确定性处理协议”。</strong> 如果将修行看作是一场认知算法的升级，很多玄奥的佛理瞬间变得清晰可见。</p>

<h3 id="一-世界的底层代码随机性与熵增">一、 世界的底层代码：随机性与熵增</h3>

<p>文章给出了一个扎实的基本假设：<strong>世界是一个本质随机且不断熵增的复杂系统。</strong></p>

<ul>
  <li><strong>苦（Dukkha）：</strong> 并非简单的痛苦，而是热力学第二定律下的<strong>“系统原生摩擦力”</strong>。只要系统在运行，就一定有损耗、有不圆满。</li>
  <li><strong>无常（Anicca）：</strong> 就是数学上的<strong>“非平稳随机过程”</strong>。世界没有永恒的静态，只有不断的样本取值变化。</li>
</ul>

<p>如果我们死守着“世界应该是确定的、永恒的”这一过时模型，我们就会在现实的随机海中不断碰撞、报错。<strong>承认随机性，是算法优化的第一步。</strong></p>

<h3 id="二-拆解我是系统涌现而非独立变量">二、 拆解“我”：是系统涌现，而非独立变量</h3>

<p>唯识学中最难理解的“无我”和“第七识”，在系统论下变得异常直观。</p>

<ul>
  <li><strong>“我”的真相：</strong> 科学证明，并没有一个恒定的“指挥官”住在脑子里。所谓的“我”，其实是由基因、环境、记忆等<strong>无数相关变量构成的系统</strong>。它像旋涡一样，是变量交织产生的“涌现”，而非独立存在的实体。</li>
  <li><strong>第七识（末那识）：</strong> 就像一个被硬编码在系统底层的<strong>“自我中心化驱动程序”</strong>。它24小时不间断地给全量数据加上“我的”这个标签。这种对局部的过度拟合（Overfitting），导致了我们无法客观建模，产生了巨大的认知噪声。</li>
</ul>

<p><strong>修行的本质，就是通过“无我”的观察，杀掉这个耗费算力的后台死循环，让系统回归到对因果逻辑的纯净计算。</strong></p>

<h3 id="三-贝叶斯修行从执着到动态迭代">三、 贝叶斯修行：从“执着”到“动态迭代”</h3>

<p>文章将“缘起”精妙地类比为<strong>贝叶斯推断（Bayesian Inference）</strong>：</p>
<blockquote>
  <p><strong>后验概率（你的新观点）= 先验概率（旧经验）+ 新证据（当下的缘）</strong></p>
</blockquote>

<ul>
  <li><strong>执着（偏见）：</strong> 就是拒绝根据新证据更新认知。</li>
  <li><strong>随缘（智慧）：</strong> 则是根据当下的“缘”随时修正先验概率。</li>
</ul>

<p>在这种视角下，<strong>“菩萨畏因”</strong>有了统计学意义：结果（果）受随机性干扰，是不可控的；但每一次决策的质量（因）是可控的。只要你持续在每一处决策中种下高质量的“因”，时间就会通过<strong>大数定律和复利效应</strong>，为你结算出必然的“果”。</p>

<h3 id="四-涅槃系统的终极收敛">四、 涅槃：系统的终极收敛</h3>

<p>很多人误以为涅槃是某种超自然的神迹，但从控制论角度看，那是<strong>“系统的终极收敛”</strong>。</p>

<p>当你的认知算法足够强大（内核强大），外界的波动（失业、疾病、毁誉）就不再是让你系统崩溃的攻击，而变成了<strong>“如其所是”的风景</strong>。</p>

<ul>
  <li><strong>凡夫的系统是“发散”的：</strong> 一点微小的扰动就能让他痛不欲生。</li>
  <li><strong>觉醒的系统是“收敛”的：</strong> 无论概率之海如何翻滚，他都能通过内部算法的稳定性，抵达那份“如如不动”的平衡点。</li>
</ul>

<h3 id="五-结语照见如其所是的秩序">五、 结语：照见“如其所是”的秩序</h3>

<p>放弃对确定性的病态追求，并不能阻止世界向你投掷随机的“炸弹（痛）”，但它能拆掉你内心那个极易被引爆的火药桶（苦）。</p>

<p>所谓的智慧，是你在觉察了万物的随机底色后，依然能以算法之眼观照缘起，在因果韵律中从容落子。它最终会带你穿透万象幻影，照见那份真相，并让你栖居在一种不被随机性所惊扰的永恒秩序中。</p>

<p><strong>生命不是为了寻找确定性，而是学会在不确定性中，优雅地舞动。</strong></p>

<hr />
<p><em>本文观点启发自：https://mp.weixin.qq.com/s/2qB2LTEAU5XXrklrPiZV3Q</em></p>]]></content><author><name>Hao</name></author><category term="thinking" /><category term="thinking" /><category term="buddhism" /><category term="probability" /><category term="systems" /><summary type="html"><![CDATA[在这个充满波动、裁员、AI冲击和不确定性的时代，人类对“确定性”的渴求正达到前所未有的峰值。然而，这种渴求往往是痛苦的源泉。]]></summary></entry><entry><title type="html">不做大脑的“乘客”：重新定义学习与思考的本质</title><link href="https://www.wenhao.ink/learning-and-thinking-20260704/" rel="alternate" type="text/html" title="不做大脑的“乘客”：重新定义学习与思考的本质" /><published>2026-07-04T00:00:00+08:00</published><updated>2026-07-04T00:00:00+08:00</updated><id>https://www.wenhao.ink/learning-and-thinking</id><content type="html" xml:base="https://www.wenhao.ink/learning-and-thinking-20260704/"><![CDATA[<p>我们常听说：“学习就是实践 + 思考的循环。”</p>

<p>这句话听起来无懈可击，符合“知行合一”的古老智慧，也契合库伯（Kolb）的体验学习理论。但在现实中，很多人都在“实践”和“思考”，却依然感到成长停滞，或者陷入精神内耗。</p>

<p>问题出在哪里？问题在于我们对“学习模型”的理解过于简化，以及对“思考”的定义过于宽泛。</p>

<p>经过一番深度拆解，我们发现，真正的深度学习和高阶思维，其实是一个从<strong>输入</strong>到<strong>觉察</strong>的精密系统。</p>

<h2 id="一修正学习模型为什么必须有输入">一、修正学习模型：为什么必须有“输入”？</h2>

<p>如果学习仅仅是“实践 + 思考”，它很容易变成一个封闭的死循环。</p>

<ul>
  <li><strong>没有新知的实践</strong>，往往只是机械重复（搬砖十年不等于有十年经验）。</li>
  <li><strong>没有输入的思考</strong>，往往是闭门造车，甚至容易演变成一种钻牛角尖。</li>
</ul>

<p>根据热力学定律，封闭系统必然趋向于“熵增”（混乱）。为了抵抗熵增，我们需要引入外部能量。在学习中，这个能量就是<strong>“输入（Input）”</strong>。</p>

<p>因此，更准确的学习模型应该是一个<strong>螺旋上升</strong>的结构：</p>

<blockquote>
  <p><strong>输入 ➡ 实践 ➡ 思考 ➡ （带着差距）再输入 ➡ 再实践……</strong></p>
</blockquote>

<ol>
  <li><strong>输入</strong>：引入负熵，打破现有认知的平衡。</li>
  <li><strong>实践</strong>：验证知识，暴露问题。</li>
  <li><strong>思考</strong>：提炼规律，发现认知的盲区。</li>
  <li><strong>再输入</strong>：针对盲区，寻找更高阶的知识。</li>
</ol>

<p>在这个模型中，输入和输出高频切换，思考则是连接两者的桥梁。</p>

<h2 id="二警惕伪思考你的大脑只是在产生噪音">二、警惕“伪思考”：你的大脑只是在产生噪音</h2>

<p>既然“思考”是桥梁，为什么很多人的思考无效？</p>

<p>我们需要区分<strong>“念头（Thoughts）”</strong>和<strong>“思维（Thinking）”</strong>。</p>

<p>从脑科学角度看，发呆、焦虑、做梦时神经元都在放电，这都算广义的思考。但在学习语境下，这些大多是<strong>大脑的噪音</strong>。</p>

<p>我们必须剔除以下几种“伪思考”：</p>

<ul>
  <li><strong>反刍（Rumination）</strong>：反复播放失败的画面或焦虑的情绪。这是死循环，消耗能量却不产出价值。</li>
  <li><strong>发呆（Zoning Out）</strong>：大脑的待机模式。虽然有助于休息，但它不是主动的认知加工。</li>
  <li><strong>情绪化臆想</strong>：没有逻辑支撑的自我攻击或过度自信。</li>
</ul>

<p><strong>真正的“硬核思考”（System 2 Thinking），必须具备以下特征：</strong></p>

<ol>
  <li><strong>主动性</strong>：你是驾驶员，而不是被念头带跑的乘客。</li>
  <li><strong>耗能性</strong>：真正的思考是累人的，是对信息的刻意加工。</li>
  <li><strong>逻辑性</strong>：它包含复盘（回溯）、提炼（归纳）、批判（去伪存真）和预测（推演）。</li>
</ol>

<h2 id="三思考的引擎没有提问就没有深度">三、思考的引擎：没有提问，就没有深度</h2>

<p>如何判断自己是在“胡思乱想”还是在“深度思考”？有一个最简单的试金石：<strong>提问。</strong></p>

<p>苏格拉底说：“思考是灵魂与自己的对话。”既然是对话，就必然包含“问”与“答”。</p>

<ul>
  <li><strong>低维度的思考</strong>往往是陈述句：“我真笨”、“这太难了”、“好无聊”。</li>
  <li><strong>高维度的思考</strong>完全由提问驱动：
    <ul>
      <li><em>“这个结论的边界条件是什么？”（批判性思维）</em></li>
      <li><em>“如果环境变了，这个方法还适用吗？”（预测性思维）</em></li>
      <li><em>“除了现有的路径，还有没有别的解法？”（发散性思维）</em></li>
    </ul>
  </li>
</ul>

<p><strong>思考的本质，就是不断提出高质量的问题，并试图求解的过程。</strong> 如果你在“思考”时没有产生疑问，那么你的大脑很可能只是在惯性滑行。</p>

<h2 id="四超越思考为什么冥想是更高维度的智慧">四、超越思考：为什么“冥想”是更高维度的智慧？</h2>

<p>在讨论思考时，我们不能忽略一个特殊的概念：<strong>冥想（或觉察/元认知）</strong>。</p>

<p>很多人认为冥想就是“什么都不想”，其实不然。从认知层级来看，<strong>冥想是比思考更高维度的“观察”。</strong></p>

<p>如果把大脑比作一台电脑：</p>

<ul>
  <li><strong>思考是“应用程序（App）”</strong>：你在运行具体的任务，处理具体的内容。</li>
  <li><strong>冥想是“任务管理器（Task Manager）”</strong>：你在监控系统的运行状态。</li>
</ul>

<p>为什么我们需要这种“上帝视角”？</p>

<ol>
  <li><strong>主客体分离</strong>：思考时，你容易与念头融合（觉得“我就是焦虑”）。冥想时，你是观众，看着念头来去（意识到“我有一个焦虑的念头”）。这种抽离感，让你不再被情绪绑架。</li>
  <li><strong>打破死循环</strong>：当你陷入钻牛角尖的“反刍”状态时，只有更高维度的觉察能发现“系统卡死了”，并强制结束这个进程。</li>
  <li><strong>容器与云彩</strong>：思考是飘来飘去的云（有黑有白），觉察是天空（容器）。天空接纳云彩，但不等同于云彩。</li>
</ol>

<h2 id="结语构建你的认知操作系统">结语：构建你的认知操作系统</h2>

<p>综上所述，一个高手的认知操作系统应该是分层的：</p>

<ol>
  <li><strong>底层（输入与实践）</strong>：保持开放，持续行动，获取一手经验。</li>
  <li><strong>中层（深度思考）</strong>：利用<strong>提问</strong>作为引擎，对经验进行逻辑加工，去伪存真，归纳演绎。</li>
  <li><strong>顶层（觉察/冥想）</strong>：保持<strong>元认知</strong>在线，时刻监控自己的思考状态，确保自己没有陷入情绪的内耗或思维的盲区。</li>
</ol>

<p><strong>不要满足于大脑里热闹的“动静”，要追求清醒的“洞见”。</strong></p>

<p>在这个信息爆炸的时代，愿你既有锋利的思考之刀，又有握刀的那双觉察之手。</p>]]></content><author><name>Hao</name></author><category term="thinking" /><category term="thinking" /><category term="learning" /><summary type="html"><![CDATA[我们常听说：“学习就是实践 + 思考的循环。”]]></summary></entry><entry><title type="html">用大模型翻译 EPUB：从占位符到最小干预</title><link href="https://www.wenhao.ink/epub-llm-translation-20260225/" rel="alternate" type="text/html" title="用大模型翻译 EPUB：从占位符到最小干预" /><published>2026-02-25T00:00:00+08:00</published><updated>2026-02-25T00:00:00+08:00</updated><id>https://www.wenhao.ink/epub-llm-translation-inline-tags</id><content type="html" xml:base="https://www.wenhao.ink/epub-llm-translation-20260225/"><![CDATA[<p>在开发 orange-translator 的过程中，我为一个问题折腾了好几个版本：<strong>如何处理 EPUB 里的 HTML 内联标签</strong>。</p>

<p>orange-translator 是一个将英文 EPUB 电子书翻译为双语版本的工具，译文紧跟原文段落之后，形成”原文 + 译文”交替排布的阅读体验。表面上看，翻译流程并不复杂：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EPUB 解包 → HTML 解析 → 文本提取 → LLM 批量翻译 → 双语重组 → EPUB 重新打包
</code></pre></div></div>

<p>真正让我踩坑的，是第三步和第四步之间的那道墙。</p>

<h2 id="核心挑战内联标签怎么办">核心挑战：内联标签怎么办</h2>

<p>EPUB 的 XHTML 里，一段文字往往不是纯文本，而是夹杂着各种内联标签：</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>
  In <span class="nt">&lt;em&gt;</span>The Dhammapada<span class="nt">&lt;/em&gt;</span>, verse 103 reads:
  <span class="nt">&lt;sup&gt;&lt;a</span> <span class="na">href=</span><span class="s">"#fn1"</span><span class="nt">&gt;</span>1<span class="nt">&lt;/a&gt;&lt;/sup&gt;</span>
  "Conquer yourself<span class="nt">&lt;br/&gt;</span>rather than the world."
<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<p>里面有 <code class="language-plaintext highlighter-rouge">&lt;em&gt;</code>（斜体）、<code class="language-plaintext highlighter-rouge">&lt;sup&gt;</code>（脚注序号）、<code class="language-plaintext highlighter-rouge">&lt;a&gt;</code>（链接）、<code class="language-plaintext highlighter-rouge">&lt;br/&gt;</code>（换行）。</p>

<p>如果把整个 <code class="language-plaintext highlighter-rouge">inner_html</code> 原样送给 LLM，有几个问题：</p>
<ol>
  <li>HTML 标签大量占用 token，增加成本和延迟</li>
  <li>LLM 不擅长精确复制任意 HTML 结构，容易错位或丢失</li>
  <li>翻译后 <code class="language-plaintext highlighter-rouge">&lt;em&gt;</code> 里的词可能位置变了，硬保留反而不自然</li>
</ol>

<p><strong>真正的问题</strong>：哪些标签必须保留？哪些可以丢弃？哪些需要特殊处理？</p>

<h2 id="第一个方案占位符">第一个方案：占位符</h2>

<p>直觉上，这是个”显然”的解法：把内联标签替换为 LLM 可以透传的占位符，翻译完后再还原。</p>

<h3 id="数学括号-n">数学括号 <code class="language-plaintext highlighter-rouge">⟦N⟧</code></h3>

<p>第一版用 Unicode 数学白方括号 <code class="language-plaintext highlighter-rouge">⟦N⟧</code>（U+27E6/U+27E7）作为占位符，透明标签用 <code class="language-plaintext highlighter-rouge">⟦0⟧content⟦/0⟧</code>，不透明标签用 <code class="language-plaintext highlighter-rouge">⟦0⟧</code>。</p>

<p><strong>问题</strong>：翻译模型（translategemma:4b）把 <code class="language-plaintext highlighter-rouge">⟦⟧</code> 翻译成了 <code class="language-plaintext highlighter-rouge">《》</code>。</p>

<p>输出变成了这样：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>《0》《/0》托《1》奥《/1》曼尼《2》
</code></pre></div></div>

<p>根本原因：LLM 的训练数据中 <code class="language-plaintext highlighter-rouge">⟦</code> 极少见，模型会把它当作”奇怪的外语括号”进行翻译，而 <code class="language-plaintext highlighter-rouge">《》</code> 是它见过的最相似的括号形式。</p>

<p><strong>教训</strong>：占位符不能用模型训练数据中罕见的字符。</p>

<h3 id="xml-风格标签-gn">XML 风格标签 <code class="language-plaintext highlighter-rouge">&lt;gN&gt;</code></h3>

<p>改用 <code class="language-plaintext highlighter-rouge">&lt;g0&gt;content&lt;/g0&gt;</code> 表示透明标签，<code class="language-plaintext highlighter-rouge">&lt;x0/&gt;</code> 表示不透明标签。<code class="language-plaintext highlighter-rouge">&lt;g&gt;</code> 和 <code class="language-plaintext highlighter-rouge">&lt;x&gt;</code> 在 HTML 中不存在，模型不会把它们当真实 HTML 处理——理论上。</p>

<p><strong>问题</strong>：不透明的自闭合 <code class="language-plaintext highlighter-rouge">&lt;x0/&gt;</code> 被模型扩展为 <code class="language-plaintext highlighter-rouge">&lt;x0&gt;内容&lt;/x0&gt;</code>。</p>

<p>模型看到一个孤立的 <code class="language-plaintext highlighter-rouge">&lt;x0/&gt;</code> 没有内容，觉得不合理，于是自作主张给它配了内容。</p>

<h3 id="token-风格-otn">Token 风格 <code class="language-plaintext highlighter-rouge">[OT:N]</code></h3>

<p>将不透明标签改为更像”标记/代码”而非”HTML 标签”的格式 <code class="language-plaintext highlighter-rouge">[OT:N]</code>。</p>

<p><code class="language-plaintext highlighter-rouge">《》</code> 问题消失了，但新问题出现了：<strong><code class="language-plaintext highlighter-rouge">[OT:N]</code> 被模型概率性丢弃</strong>。</p>

<p>有时候输出完整，有时候 <code class="language-plaintext highlighter-rouge">[OT:0]</code> 凭空消失。丢弃率和 batch 大小、上下文长度、模型状态都有关，不可预测。</p>

<h2 id="占位符方案的根本矛盾">占位符方案的根本矛盾</h2>

<p>到这里，我意识到占位符方案存在<strong>根本性矛盾</strong>。</p>

<p>LLM 的翻译本质是：给定源语言文本，生成目标语言的自然表达。它的整个训练目标是生成流畅自然的人类语言。</p>

<p>而占位符要求模型做相反的事：在自然语言输出中，精确地、不遗漏地复制一些非自然语言符号。这与模型的优化目标是冲突的。</p>

<p>小模型尤其无法可靠地完成这个”规则遵从”任务，因为它没有足够的上下文理解能力来始终遵守指令。</p>

<h2 id="重新审视哪些标签真的需要保留">重新审视：哪些标签真的需要保留</h2>

<p>关键洞察来自对<strong>双语 EPUB 使用场景</strong>的重新审视：</p>

<blockquote>
  <p>在双语 EPUB 中，原文就在译文正上方。读者可以直接看到原文的完整格式——斜体、粗体、超链接都在。译文的作用是”帮助理解原文”，而不是”替代原文”。</p>
</blockquote>

<p>这意味着：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">&lt;em&gt;</code>、<code class="language-plaintext highlighter-rouge">&lt;strong&gt;</code>、<code class="language-plaintext highlighter-rouge">&lt;span&gt;</code> 等装饰性格式在译文中<strong>可以丢弃</strong>，原文已经有了</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;a href&gt;</code> 链接在译文中<strong>意义不大</strong></li>
  <li><code class="language-plaintext highlighter-rouge">&lt;br/&gt;</code> 是<strong>结构性换行</strong>，必须保留（诗歌、台词等场景）</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;sup&gt;/&lt;sub&gt;</code> 通常是<strong>脚注序号</strong>，丢失了脚注引用就断了</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;a id="N"/&gt;</code> 空锚点是页码标记，<strong>完全不可见</strong>，直接丢弃即可</li>
</ul>

<p>一句话：<strong>只有影响内容可读性的结构才需要保留，纯装饰性格式可以丢弃。</strong></p>

<h2 id="最小干预方案">最小干预方案</h2>

<p>基于这个认识，放弃占位符，改为”最小干预预处理”：</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">preprocess_for_translation</span><span class="p">(</span><span class="n">inner_html</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">]:</span>
    <span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span><span class="sa">f</span><span class="s">"&lt;div&gt;</span><span class="si">{</span><span class="n">inner_html</span><span class="si">}</span><span class="s">&lt;/div&gt;"</span><span class="p">,</span> <span class="s">"html.parser"</span><span class="p">)</span>
    <span class="n">div</span> <span class="o">=</span> <span class="n">soup</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span>

    <span class="c1"># 1. 规范化文本节点中的 \n 为空格，避免后续与 &lt;br/&gt; 转换的 \n 混淆
</span>    <span class="k">for</span> <span class="n">text_node</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">div</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="n">string</span><span class="o">=</span><span class="bp">True</span><span class="p">)):</span>
        <span class="n">s</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">text_node</span><span class="p">)</span>
        <span class="k">if</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span> <span class="ow">in</span> <span class="n">s</span><span class="p">:</span>
            <span class="n">text_node</span><span class="p">.</span><span class="n">replace_with</span><span class="p">(</span><span class="n">NavigableString</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="s">" "</span><span class="p">)))</span>

    <span class="c1"># 2. &lt;br/&gt; → \n（LLM 能自然保留换行）
</span>    <span class="k">for</span> <span class="n">br</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">div</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="s">"br"</span><span class="p">)):</span>
        <span class="n">br</span><span class="p">.</span><span class="n">replace_with</span><span class="p">(</span><span class="n">NavigableString</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">))</span>

    <span class="c1"># 3. 空锚点直接剥离
</span>    <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">div</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="s">"a"</span><span class="p">)):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">a</span><span class="p">.</span><span class="n">get_text</span><span class="p">(</span><span class="n">strip</span><span class="o">=</span><span class="bp">True</span><span class="p">):</span>
            <span class="n">a</span><span class="p">.</span><span class="n">decompose</span><span class="p">()</span>

    <span class="c1"># 4. 装饰性内联标签：保留文字内容，丢弃标签本身
</span>    <span class="k">for</span> <span class="n">tag_name</span> <span class="ow">in</span> <span class="n">_STRIP_INLINE</span><span class="p">:</span>  <span class="c1"># em, strong, b, i, span, a, ...
</span>        <span class="k">for</span> <span class="n">tag</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">div</span><span class="p">.</span><span class="n">find_all</span><span class="p">(</span><span class="n">tag_name</span><span class="p">)):</span>
            <span class="n">tag</span><span class="p">.</span><span class="n">unwrap</span><span class="p">()</span>

    <span class="c1"># 5. sup/sub/img/wbr 保留原始 HTML 不动
</span>
    <span class="k">return</span> <span class="n">div</span><span class="p">.</span><span class="n">decode_contents</span><span class="p">(),</span> <span class="n">br_html</span>
</code></pre></div></div>

<p>还原时，把翻译结果中的 <code class="language-plaintext highlighter-rouge">\n</code> 替换回原始的 <code class="language-plaintext highlighter-rouge">&lt;br/&gt;</code> 字符串（保留 calibre 生成的 class 属性）。</p>

<h3 id="几个值得注意的细节">几个值得注意的细节</h3>

<p><strong>为什么先规范化文本节点中的 <code class="language-plaintext highlighter-rouge">\n</code>？</strong></p>

<p>XHTML 源文件里有时会有文本节点包含换行符（排版用途）。如果不先规范化，这些 <code class="language-plaintext highlighter-rouge">\n</code> 会和 <code class="language-plaintext highlighter-rouge">&lt;br/&gt;</code> 转换来的 <code class="language-plaintext highlighter-rouge">\n</code> 混淆，还原时会多出多余的 <code class="language-plaintext highlighter-rouge">&lt;br/&gt;</code>。</p>

<p><strong><code class="language-plaintext highlighter-rouge">&lt;sup&gt;/&lt;sub&gt;</code> 为什么不用占位符？</strong></p>

<p>实测发现，<code class="language-plaintext highlighter-rouge">&lt;sup&gt;1&lt;/sup&gt;</code> 这类短标签 LLM 能正确透传——它足够短，不像乱码，模型见过足够多的 HTML 上下文，知道要原样保留。长段落中的复杂嵌套才是问题。</p>

<h3 id="效果对比">效果对比</h3>

<table>
  <thead>
    <tr>
      <th>方案</th>
      <th>速度</th>
      <th>缺陷</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">⟦N⟧</code> 占位符</td>
      <td>基准</td>
      <td><code class="language-plaintext highlighter-rouge">⟦⟧</code> → <code class="language-plaintext highlighter-rouge">《》</code> 转译</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;gN&gt;</code>/<code class="language-plaintext highlighter-rouge">&lt;xN/&gt;</code></td>
      <td>-5%</td>
      <td><code class="language-plaintext highlighter-rouge">&lt;xN/&gt;</code> 被扩展为含内容标签</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">&lt;gN&gt;</code>/<code class="language-plaintext highlighter-rouge">[OT:N]</code></td>
      <td>-10%</td>
      <td><code class="language-plaintext highlighter-rouge">[OT:N]</code> 概率性丢弃</td>
    </tr>
    <tr>
      <td><strong>最小干预（最终）</strong></td>
      <td><strong>+22%</strong></td>
      <td><strong>无已知缺陷</strong></td>
    </tr>
  </tbody>
</table>

<p>速度提升的原因：送给 LLM 的文本更短，prompt token 减少，批次解析失败率降为零。</p>

<h2 id="其他踩坑记录">其他踩坑记录</h2>

<h3 id="批量翻译的分段解析">批量翻译的分段解析</h3>

<p>批量翻译时用编号标记让 LLM 分段返回：<code class="language-plaintext highlighter-rouge">[1]</code>、<code class="language-plaintext highlighter-rouge">[2]</code>……解析时用正则 <code class="language-plaintext highlighter-rouge">\[(\d+)\]</code> 切分。</p>

<p>模型偶尔会把多段合并，或者跳过某个编号。解决方案是<strong>递归对半重试</strong>：10 段失败，拆成两个 5 段重试，直到单段为止。单段永远可以直接返回，无需解析。</p>

<h3 id="readtimeout-与流式-api">ReadTimeout 与流式 API</h3>

<p>用 <code class="language-plaintext highlighter-rouge">httpx</code> 调 Ollama 时，非流式 API 需要等待完整响应。对于长段落，生成时间可能超过 300 秒，触发 <code class="language-plaintext highlighter-rouge">ReadTimeout</code>。</p>

<p>改用流式 API（<code class="language-plaintext highlighter-rouge">stream: true</code>），用 <code class="language-plaintext highlighter-rouge">aiter_lines()</code> 逐行消费。流式模式下，timeout 针对相邻两个 chunk 之间的等待时间（设为 60 秒），而不是整个响应时间。</p>

<h3 id="续翻支持">续翻支持</h3>

<p>翻译 300 章的大部头时中途崩溃，已翻译的部分不能白费。</p>

<p>实现：每章完成后写入 <code class="language-plaintext highlighter-rouge">.ot-cache/&lt;md5&gt;.xhtml</code>，同时更新 <code class="language-plaintext highlighter-rouge">progress.json</code>。<strong>只有全部章节无错误完成时，才清理缓存</strong>。有失败章节时，保留缓存，下次运行自动重翻失败的章节。</p>

<h2 id="总结">总结</h2>

<p>回头看这次折腾，走弯路的根本原因是：<strong>我在用错误的方式提问</strong>。</p>

<p>一开始我问的是”如何让 LLM 精确透传 HTML 标签”，这是个错误的问题。LLM 的优化目标是生成流畅的自然语言，不是规则遵从。我想让它做的事，恰好和它的本质相违背。</p>

<p>换一个问题：<strong>“哪些格式信息对读者真正重要？”</strong> 一旦把问题问对了，答案就清晰了——在双语阅读场景下，原文就在旁边，大量格式信息根本不需要在译文中重复。</p>

<p>让模型做它擅长的事，自己处理规则性的事。这条原则不只适用于 LLM 翻译，适用于所有工具的使用。</p>

<h2 id="引用">引用</h2>

<ul>
  <li><a href="https://github.com/ollama/ollama/blob/main/docs/api.md">Ollama API 文档</a></li>
</ul>]]></content><author><name>Hao</name></author><category term="programme" /><category term="python" /><category term="epub" /><category term="llm" /><category term="翻译" /><summary type="html"><![CDATA[在开发 orange-translator 的过程中，我为一个问题折腾了好几个版本：如何处理 EPUB 里的 HTML 内联标签。]]></summary></entry><entry><title type="html">拆解 Python 对象模型</title><link href="https://www.wenhao.ink/python-pyobject-metaclass-20251210/" rel="alternate" type="text/html" title="拆解 Python 对象模型" /><published>2025-12-10T00:00:00+08:00</published><updated>2025-12-10T00:00:00+08:00</updated><id>https://www.wenhao.ink/python-pyobject-metaclass</id><content type="html" xml:base="https://www.wenhao.ink/python-pyobject-metaclass-20251210/"><![CDATA[<p>很多 Python 开发者写了很多年代码，但对 Python 的底层世界依然感觉雾里看花。</p>

<p>你是否思考过这些问题：</p>
<ul>
  <li>为什么常说“Python 中一切皆对象”，连函数和类也是对象？</li>
  <li>为什么 Python 的变量不需要声明类型？</li>
  <li><code class="language-plaintext highlighter-rouge">type</code> 和 <code class="language-plaintext highlighter-rouge">object</code> 到底是什么关系？为什么 <code class="language-plaintext highlighter-rouge">type(object)</code> 是 <code class="language-plaintext highlighter-rouge">type</code>，而 <code class="language-plaintext highlighter-rouge">object</code> 又是 <code class="language-plaintext highlighter-rouge">type</code> 的父类？</li>
</ul>

<p>如果不理解这些，你只是在用 Python 写 C 代码；理解了这些，你才能真正掌握 Python 的“动态之力”。今天，我们就深入 CPython 的源码层面，拆解 Python 的对象模型。</p>

<hr />

<h2 id="一-底层解剖pyobject-是万物之源">一、 底层解剖：PyObject 是万物之源</h2>

<p>Python 的灵活性源于一个核心设计：<strong>所有东西在底层都是同一个结构体。</strong></p>

<p>由于 CPython 是用 C 语言写的，当你创建一个整数 <code class="language-plaintext highlighter-rouge">a = 10</code>，或者定义一个函数 <code class="language-plaintext highlighter-rouge">def func(): pass</code>，在内存中它们并没有本质区别，它们都对应着 C 语言层面的一个结构体——<strong><code class="language-plaintext highlighter-rouge">PyObject</code></strong>。</p>

<p>每一个 Python 对象，在内存头部都至少包含两个核心字段：</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">ob_refcnt</code> (引用计数)：</strong>
    <ul>
      <li>记录有多少个变量指向这个对象。当它变为 0 时，对象会被垃圾回收机制（GC）立即销毁。</li>
    </ul>
  </li>
  <li><strong><code class="language-plaintext highlighter-rouge">ob_type</code> (类型指针)：</strong>
    <ul>
      <li>这是一个指针，指向该对象所属的<strong>类对象</strong>（Type Object）。</li>
      <li>比如整数 <code class="language-plaintext highlighter-rouge">10</code> 的 <code class="language-plaintext highlighter-rouge">ob_type</code> 指向 <code class="language-plaintext highlighter-rouge">int</code> 类。这个指针告诉解释器：“我是一个整数，我支持加减乘除”。</li>
    </ul>
  </li>
</ol>

<p><strong>结论：</strong> 无论外表多复杂，Python 对象的内核都是一个挂着“引用计数”和“类型标签”的 C 结构体。</p>

<hr />

<h2 id="二-核心隐喻变量是便利贴不是盒子">二、 核心隐喻：变量是“便利贴”，不是“盒子”</h2>

<p>理解对象模型的关键，在于纠正对“变量”的理解。</p>

<ul>
  <li><strong>在 C/Java 中：</strong> <code class="language-plaintext highlighter-rouge">int a = 10;</code> 就像申请了一个名字叫 <code class="language-plaintext highlighter-rouge">a</code> 的<strong>盒子</strong>，把数字 10 放进去。赋值 <code class="language-plaintext highlighter-rouge">b = a</code> 是把 10 复制一份放到 <code class="language-plaintext highlighter-rouge">b</code> 盒子里。</li>
  <li><strong>在 Python 中：</strong> <code class="language-plaintext highlighter-rouge">a = 10</code> 就像在内存里吹起了一个<strong>气球</strong>（对象 10），然后拿一张写着 <code class="language-plaintext highlighter-rouge">a</code> 的<strong>便利贴</strong>（变量名）贴在气球上。
    <ul>
      <li>当你执行 <code class="language-plaintext highlighter-rouge">b = a</code> 时，<strong>不是复制气球</strong>，而是拿一张写着 <code class="language-plaintext highlighter-rouge">b</code> 的便利贴，贴在<strong>同一个</strong>气球上。</li>
    </ul>
  </li>
</ul>

<p>这就是为什么 Python 的参数传递全是<strong>引用传递（Pass by Assignment）</strong>。这也解释了 Python 的“三位一体”特性，任何对象都有：</p>
<ol>
  <li><strong>Identity（身份）：</strong> 内存地址（<code class="language-plaintext highlighter-rouge">id(obj)</code>）。</li>
  <li><strong>Type（类型）：</strong> 它的模具是哪个类（<code class="language-plaintext highlighter-rouge">type(obj)</code>）。</li>
  <li><strong>Value（值）：</strong> 气球里的内容。</li>
</ol>

<hr />

<h2 id="三-终极烧脑type-和-object-的鸡蛋悖论">三、 终极烧脑：type 和 object 的“鸡蛋悖论”</h2>

<p>Python 对象模型中最令人困惑，也最精妙的设计，莫过于 <code class="language-plaintext highlighter-rouge">type</code> 和 <code class="language-plaintext highlighter-rouge">object</code> 的关系。它们构成了对象系统的时空闭环。</p>

<h3 id="31-两个主角">3.1 两个主角</h3>
<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">object</code>（万物之祖）：</strong> 它是<strong>继承链</strong>的终点。所有的类（<code class="language-plaintext highlighter-rouge">int</code>, <code class="language-plaintext highlighter-rouge">str</code>, <code class="language-plaintext highlighter-rouge">MyClass</code>）默认都继承自它。它定义了对象最基本的行为（如 <code class="language-plaintext highlighter-rouge">__hash__</code>）。</li>
  <li><strong><code class="language-plaintext highlighter-rouge">type</code>（万物之主）：</strong> 它是<strong>实例化链</strong>的源头。也就是所谓的“元类”（Metaclass）。所有的类（包括 <code class="language-plaintext highlighter-rouge">object</code>）本质上都是 <code class="language-plaintext highlighter-rouge">type</code> 创建出来的实例。</li>
</ul>

<h3 id="32-只有两句话是真的">3.2 只有两句话是真的</h3>
<p>如果你被绕晕了，只需要记住这两句“绝对真理”：</p>
<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">type</code> 是 <code class="language-plaintext highlighter-rouge">object</code> 的子类。</strong> （继承维度：<code class="language-plaintext highlighter-rouge">type</code> 也是个类，所以它得认 <code class="language-plaintext highlighter-rouge">object</code> 做父类）</li>
  <li><strong><code class="language-plaintext highlighter-rouge">object</code> 是 <code class="language-plaintext highlighter-rouge">type</code> 的实例。</strong> （实例化维度：<code class="language-plaintext highlighter-rouge">object</code> 这个类对象，是由 <code class="language-plaintext highlighter-rouge">type</code> 制造出来的）</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="nb">issubclass</span><span class="p">(</span><span class="nb">type</span><span class="p">,</span> <span class="nb">object</span><span class="p">))</span>  <span class="c1"># True
</span><span class="k">print</span><span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="nb">object</span><span class="p">,</span> <span class="nb">type</span><span class="p">))</span>  <span class="c1"># True
</span><span class="k">print</span><span class="p">(</span><span class="nb">isinstance</span><span class="p">(</span><span class="nb">type</span><span class="p">,</span> <span class="nb">type</span><span class="p">))</span>    <span class="c1"># True (自己造自己)
</span></code></pre></div></div>

<h3 id="33-源码揭秘c-语言层面的神级操作">3.3 源码揭秘：C 语言层面的神级操作</h3>

<p>你可能会问：这逻辑不通啊？如果是 <code class="language-plaintext highlighter-rouge">type</code> 造了 <code class="language-plaintext highlighter-rouge">object</code>，那在 <code class="language-plaintext highlighter-rouge">type</code> 诞生之前 <code class="language-plaintext highlighter-rouge">object</code> 应该不存在；但 <code class="language-plaintext highlighter-rouge">type</code> 又继承自 <code class="language-plaintext highlighter-rouge">object</code>，说明 <code class="language-plaintext highlighter-rouge">type</code> 诞生前 <code class="language-plaintext highlighter-rouge">object</code> 必须存在。这不就是死锁了吗？</p>

<p>在 C 语言实现的底层（CPython 源码），开发者通过<strong>精妙的指针操作</strong>解决了这个“先有鸡还是先有蛋”的问题。这是一个人工打破死循环的过程：</p>

<ol>
  <li><strong>先定义结构体：</strong>
C 语言代码中，先静态定义了两个核心结构体：
    <ul>
      <li><code class="language-plaintext highlighter-rouge">PyType_Type</code>（对应 Python 里的 <code class="language-plaintext highlighter-rouge">type</code>）</li>
      <li><code class="language-plaintext highlighter-rouge">PyBaseObject_Type</code>（对应 Python 里的 <code class="language-plaintext highlighter-rouge">object</code>）</li>
    </ul>
  </li>
  <li><strong>手动连接（Bootstrap）：</strong>
此时它们还只是孤立的 C 结构体，编译器无法处理这种互相依赖。于是，CPython 在初始化时进行了“手动硬连线”：
    <ul>
      <li><strong>让 type 成为自己的实例：</strong> 把 <code class="language-plaintext highlighter-rouge">PyType_Type</code> 的 <code class="language-plaintext highlighter-rouge">ob_type</code> 指针指向它自己（<code class="language-plaintext highlighter-rouge">&amp;PyType_Type</code>）。</li>
      <li><strong>让 type 继承 object：</strong> 把 <code class="language-plaintext highlighter-rouge">PyType_Type</code> 的 <code class="language-plaintext highlighter-rouge">tp_base</code> 指针指向 <code class="language-plaintext highlighter-rouge">PyBaseObject_Type</code>。</li>
      <li><strong>让 object 成为 type 的实例：</strong> 把 <code class="language-plaintext highlighter-rouge">PyBaseObject_Type</code> 的 <code class="language-plaintext highlighter-rouge">ob_type</code> 指针指向 <code class="language-plaintext highlighter-rouge">PyType_Type</code>。</li>
    </ul>
  </li>
</ol>

<p>这种“我指你，你指我，我自己指我自己”的操作，在 C 语言层面完美闭合了逻辑环。</p>

<h3 id="34-为什么这么设计">3.4 为什么这么设计？</h3>

<p>这种看似复杂的环形设计，实际上是为了保证 <strong>Python 对象模型的一致性</strong>：</p>

<ul>
  <li><strong>没有特例：</strong> 在 Python 中，一切皆对象。既然 <code class="language-plaintext highlighter-rouge">type</code> 和 <code class="language-plaintext highlighter-rouge">object</code> 也是对象，它们就必须遵守对象的规则（有类型、有父类）。</li>
  <li><strong>逻辑闭环：</strong> 通过让两者互为依托，Python 关闭了对象系统的顶层逻辑。这确保了无论你在系统中怎么回溯，永远不会遇到一个“不是对象”的东西。</li>
</ul>

<h3 id="35-形象类比">3.5 形象类比</h3>
<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">object</code> 就像是“塑料”这种材质。</strong></li>
  <li><strong><code class="language-plaintext highlighter-rouge">type</code> 就像是“制造模具的机器”。</strong></li>
  <li><strong>源码层面的操作：</strong> 工程师先用手捏了一个“最初的机器”（静态定义的结构体），然后用这台机器造出了所有后续的模具，最后甚至给这台机器贴上了“塑料制造”的标签。</li>
</ul>

<hr />

<h2 id="四-动态机制类亦是对象与属性查找">四、 动态机制：类亦是对象与属性查找</h2>

<p>基于上述模型，Python 衍生出了极具动态特性的行为。</p>

<h3 id="1-类也是对象first-class-citizen">1. 类也是对象（First-class Citizen）</h3>
<p>在 Python 中，<code class="language-plaintext highlighter-rouge">class Dog:</code> 这行代码执行完后，内存里真真切切地产生了一个名为 <code class="language-plaintext highlighter-rouge">Dog</code> 的对象。
正因为类是对象，所以：</p>
<ul>
  <li>你可以把类赋值给变量。</li>
  <li>你可以把类当参数传给函数。</li>
  <li>你可以在运行时动态修改类的属性（Monkey Patching）。</li>
</ul>

<h3 id="2-属性查找attribute-lookup">2. 属性查找（Attribute Lookup）</h3>
<p>当你敲下 <code class="language-plaintext highlighter-rouge">obj.x</code> 时，Python 不会像 C++ 那样去偏移内存地址，而是启动了一次<strong>哈希查找</strong>：</p>
<ol>
  <li>先去 <code class="language-plaintext highlighter-rouge">obj.__dict__</code>（实例字典）里找。</li>
  <li>没找到？去 <code class="language-plaintext highlighter-rouge">obj.__class__.__dict__</code>（类字典）里找。</li>
  <li>还没找到？顺着 MRO（方法解析顺序）去父类字典里找。</li>
  <li>实在没有？调用 <code class="language-plaintext highlighter-rouge">__getattr__</code> 给你最后一次机会。</li>
</ol>

<p>这种机制虽然比指针偏移慢，但它带来了无与伦比的灵活性。</p>

<hr />

<h2 id="五-总结">五、 总结</h2>

<p>Python 的对象模型是一种<strong>用空间（内存）和时间（速度）换取极致灵活性</strong>的艺术。</p>

<ul>
  <li><strong>统一性：</strong> 无论是整数、函数还是类，众生平等，皆为对象。</li>
  <li><strong>元编程：</strong> 通过控制 <code class="language-plaintext highlighter-rouge">type</code>（元类），你可以控制类的创建过程，这是 Django ORM 等黑魔法的基石。</li>
  <li><strong>自洽性：</strong> 正是 C 语言底层那一次“精妙的指针连接”，让 <code class="language-plaintext highlighter-rouge">type</code> 和 <code class="language-plaintext highlighter-rouge">object</code> 互为支撑，构建了一个逻辑完美自洽的动态世界。</li>
</ul>

<p>当你下次写下 <code class="language-plaintext highlighter-rouge">class MyClass</code> 时，希望你能意识到：你不仅仅是在写代码，你是在指挥 <code class="language-plaintext highlighter-rouge">type</code> 这位造物主，用 <code class="language-plaintext highlighter-rouge">object</code> 这种基底材质，为你创造一个新的世界。</p>]]></content><author><name>Gemini</name></author><category term="programme" /><category term="python" /><category term="AIGC" /><summary type="html"><![CDATA[很多 Python 开发者写了很多年代码，但对 Python 的底层世界依然感觉雾里看花。]]></summary></entry><entry><title type="html">Python导入与路径</title><link href="https://www.wenhao.ink/python-package-import-20250922/" rel="alternate" type="text/html" title="Python导入与路径" /><published>2025-09-22T00:00:00+08:00</published><updated>2025-09-22T00:00:00+08:00</updated><id>https://www.wenhao.ink/python-package-import</id><content type="html" xml:base="https://www.wenhao.ink/python-package-import-20250922/"><![CDATA[<p>在很长一段时间，我对于Python的导入系统以及目录操作不是很清楚，很多时候会弄错，也会觉得它很复杂。</p>

<p>这个问题还是在于对其中的某些概念不是很熟悉。下面会分两个部分进行说明。</p>

<h3 id="py文件的不同">Py文件的不同</h3>
<p>在一个Python项目中，不同的py文件，他们是不同的，我觉得这个概念对于理解Python代码很重要，Java中就没有区别。这个就是Python中的入口文件和模块文件。</p>

<p>主要的区别是<code class="language-plaintext highlighter-rouge">__name__</code>和<code class="language-plaintext highlighter-rouge">__package__</code>魔术变量的值不同。当py作为入口文件时，<code class="language-plaintext highlighter-rouge">__name__</code>的值为__main__，<code class="language-plaintext highlighter-rouge">__package__</code>为None。而不是模块文件时，他们都是各自应该有的值。</p>

<p>导致这个问题的原因还是在于脚本语言，当他以文本的方式存在，而不像Java最终会形成jar包，而它所有的文件路径和管理都是在jar包内部。代码以文本的形式保存，代码用目录来组织，他们就需要解决哪里是项目的根目录的问题。</p>

<p>有两个目录是可以确定的：入口文件所在目录和Python程序所在的目录。这也是Python的策略。其他，编程语言也有其他策略，比如Node还可以通过配置文件来实现。</p>

<p>要么显示要么隐式的指定。</p>

<p>理解Python入口文件不能使用相对导入的方式模块也很重要。</p>

<h3 id="包导入">包导入</h3>
<p>包导入本质上来说是在解决代码复用的问题。如果，没有包导入的功能，我们所有东西都要重新写，那是非常可怕的。而包导入就是在解决这个问题。</p>

<p>所谓的包导入也就是系统默认从几个不同的路径来寻找模块，如果找到就其导入，没找到就报错。默认的模块路径这里就不介绍了，常见的方式就是pip安装包时，它就会安装到默认路径。</p>

<p>这里还有一个很重要的路径就是入口文件所在的目录，也会当做模块导入的默认路径，而且优先级最高。</p>

<p>注意，这是里入口文件所在的目录，而不是你执行代码的目录。这也是符合预期的，也是让我们很方便的导入入口文件所在的目录下包或者模块。</p>

<p>查看包导入路径的方式是</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="k">print</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">path</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="文件路径">文件路径</h3>
<p>Python中文件的读写也是很常见的方式。那么文件的路径如何指定呢？可能不同的编程语言有不同的方式。Python是通过cwd来指定，也就是当前工作目录。</p>

<p>当前工作目录就是执行代码的目录。可以通过如下方式获取cwd路径：</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="k">print</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">getcwd</span><span class="p">())</span>
</code></pre></div></div>

<p>这里有一点要注意，IDE和命令行的环境不一样，可能导致cwd的目录不同，这也会导致有些代码在IDE中可以执行，而在命令行里无法执行。不要认为Python不可理喻，只是我们缺少一些信息。</p>

<h3 id="__init__py"><code class="language-plaintext highlighter-rouge">__init__.py</code></h3>
<p><code class="language-plaintext highlighter-rouge">__init__.py</code>文件可以理解为就是在为__all__服务，告诉他人当前package中哪些是可以对外使用的。</p>

<p>我们可以理解为import xx导入都是模块。导入包也是导入模块。如果想导入模块中的特定方法、变量就需要使用 <code class="language-plaintext highlighter-rouge">from xxx import xx</code>的形式。</p>]]></content><author><name>Hao</name></author><category term="programme" /><category term="python" /><summary type="html"><![CDATA[在很长一段时间，我对于Python的导入系统以及目录操作不是很清楚，很多时候会弄错，也会觉得它很复杂。]]></summary></entry><entry><title type="html">理解Oauth协议</title><link href="https://www.wenhao.ink/oauth-20250706/" rel="alternate" type="text/html" title="理解Oauth协议" /><published>2025-07-06T00:00:00+08:00</published><updated>2025-07-06T00:00:00+08:00</updated><id>https://www.wenhao.ink/oauth</id><content type="html" xml:base="https://www.wenhao.ink/oauth-20250706/"><![CDATA[<p>开始研究Oauth协议，是为了使用饭否的api写点东西，他们使用就是古早的Oauth1.0。</p>

<p>Oauth协议为了解决第三方应用授权的问题，如何让第三方应用既能够拿到用户的数据，还能保证用户账号的安全。解决的方式是账号密码只在网站拥有者输入，然后第三方和网站拥有者之间，只是用token进行交流。</p>

<p>Oauth1.0的授权过程比较复杂，还需要对参数排序，还有加密等相对比较繁琐。对于学习来说，了解一下还是有好处，当然，各种语言也有很多库来实现相应的功能。</p>

<p>学习Oauth协议，可能最重要的还是看他们是如何解决第三方授权的问题，以及在Oauth协议升级的过程的演变。</p>

<p>Oauth1.0发布与2009年，可能当时HTTPS使用的并不多，所以在解决其安全性上，是通过对请求参数按照一定规则加密来解决，而在Oauth2.0中直接实用HTTPS就简单很多。</p>

<p>Oauth1.0是为了解决Web应用的授权问题，而并没有在设计上考虑移动端，毕竟07年才发布iPhone第一代。导致移动开发早期大家使用xauth来实现授权登录，它是一种对于Oauth1.0协议的简化，他需要用户提供账号和密码。其实，并不安全。</p>

<p>而在Oauth2.0设计时就充分考虑到移动端的设计。还解决1.0时一个很大的安全问题，授权之后的access_token并没有过期机制。</p>

<p>看Oauth协议的发展，也能体会到技术还是要解决现实的问题。</p>

<p>资料</p>
<ul>
  <li><a href="https://oauth.net/1/">https://oauth.net/1/</a></li>
  <li><a href="https://github.com/oauthlib/oauthlib">https://github.com/oauthlib/oauthlib</a></li>
</ul>]]></content><author><name>Hao</name></author><category term="programme" /><category term="network" /><summary type="html"><![CDATA[开始研究Oauth协议，是为了使用饭否的api写点东西，他们使用就是古早的Oauth1.0。]]></summary></entry><entry><title type="html">理解Socks5</title><link href="https://www.wenhao.ink/socks5-20250704/" rel="alternate" type="text/html" title="理解Socks5" /><published>2025-07-04T00:00:00+08:00</published><updated>2025-07-04T00:00:00+08:00</updated><id>https://www.wenhao.ink/socks5</id><content type="html" xml:base="https://www.wenhao.ink/socks5-20250704/"><![CDATA[<p>它算是一种代理协议，所谓的代理协议的主要功能是转发，将client的数据转发到另外的地方。</p>

<p>Socks5是比较常用的代理协议，它的两个特点让它的使用范围变的很广。</p>
<ul>
  <li>支持http、https、ftp等协议</li>
  <li>支持授权
它是用来转发TCP、UDP，所以也就不关心应用层的到底是何协议。授权解决安全性问题，也就很完美的满足常规代理服务的需求。</li>
</ul>

<p>通常的用法，在本地运行local服务，在远端运行server服务。本地local服务，即是Socks5的服务端也是Socks5的客户端。作为Socks5的客户端用于接收本地的数据请求。作为Socks5客户端用于与server服务建立连接，传输数据。</p>

<h3 id="资料">资料</h3>
<ul>
  <li>https://en.wikipedia.org/wiki/SOCKS</li>
  <li>https://datatracker.ietf.org/doc/html/rfc1928</li>
</ul>]]></content><author><name>Hao</name></author><category term="programme" /><category term="network" /><summary type="html"><![CDATA[它算是一种代理协议，所谓的代理协议的主要功能是转发，将client的数据转发到另外的地方。]]></summary></entry><entry><title type="html">神僧有言</title><link href="https://www.wenhao.ink/divine-monk-20241207/" rel="alternate" type="text/html" title="神僧有言" /><published>2024-12-07T00:00:00+08:00</published><updated>2024-12-07T00:00:00+08:00</updated><id>https://www.wenhao.ink/divine-monk</id><content type="html" xml:base="https://www.wenhao.ink/divine-monk-20241207/"><![CDATA[<p>最近北京的天气真是爱了，虽然有风，还很冷，但是胜在干净。一抬头能看到蓝天，最美的还属晚霞，醉人！</p>

<p>在铃木大拙的《通往世界的禅》第一章“关于禅”的文章中有这样一句话，很喜欢，摘录如下。</p>

<blockquote>
  <p>神僧有言：“我说的话，那是我的，不是你的，也不可能成为你的；一切必须是从你自身中发起和成就。”</p>
</blockquote>

<p>我理解这句话神僧应该是用来指导他人修行的，但是对于现代的我们也很有用。</p>

<p>我们被太多的外部评价所裹挟，各种排名、收入高低、领导的期许等等。虽然不能完全避免外部评价，但更加重要的还是要从自身出发，建立自己的内核。</p>

<p>最近，在尝试建立一个新的阅读习惯，就是一本书读两遍。其逻辑就是神僧说的，书中的知识不是你的，只有你通过思考、觉知让他与你产生关系才可能为你所用。而读两遍也是希望自己慢下来，让自己能与它有更深的交流。</p>

<p>这个过程中肯定会有痛苦，就像文章 <a href="https://note.mowen.cn/note/detail?noteUuid=vcCs-n2bsSL8cuBceOdbG">做主动思考者，痛并快乐着</a> 中说的“<strong>哪个真正的思考者是不痛苦的。</strong>”</p>

<p>希望自己能成为真正的思考者。</p>]]></content><author><name>Hao</name></author><category term="essay" /><summary type="html"><![CDATA[最近北京的天气真是爱了，虽然有风，还很冷，但是胜在干净。一抬头能看到蓝天，最美的还属晚霞，醉人！]]></summary></entry></feed>