MoreRSS

site iconFrost Ming修改

Python 开发工程师,pdm 作者,坐标深圳。平时喜欢折腾技术,写写技术文章。也是个开源爱好者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Frost Ming的 RSS 预览

创造一只龙虾,需要些什么?

2026-02-12 08:00:00

import Tweet from "@components/Tweet.astro"; import LinkCard from "@components/LinkCard.astro";

稍微地介绍一下背景知识,标题中的龙虾指的是这段时间大火的 OpenClaw 项目,它最早的名字叫 ClawdBot。它一是个能与你在聊天软件中交流,从而帮你完成各种任务的通用型 AI 智能体。 最火的那段时间我并没有用它,一周过去了,我在想如何复刻它,我研究了另一个最小化复现的项目 Nanobot,我觉得我脑中大概有了蓝图。刚好朋友 PsiACE 之前曾做过一个 小型的 Agent 项目 Bub,我觉得非常适合把我的想法在它的基础上落地,也与 PsiACE 一拍即合,着手把这个 Agent 变成一个龙虾。

短短几天过去,Bub 已经达到了一个非常不错的状态。在这个过程中,我自己对 Agent 和 AI native 也发生了天翻地覆的变化。从 ChatGPT 诞生之初的 Chatbot,到强力编程助手 Claude Code,到现在的 OpenClaw,AI 应用的形态也在不断进化。但不得不承认这个变化的每一步都是新的思维模式,这种转弯需要适应。所以我也谨通过这篇文章,来分享一下我思维的转变。

初步复刻

首先请大家思考一个问题,假设你已有一个 Agent(指 Codex 和 Claude Code 这样的东西),现在要给他增加 Telegram 聊天能力,你会怎么做?

有经验的开发者会发现这不是什么难事,只要在 Agent 的基础上增加 Telegram message handler,把 Telegram 收到的消息转给 Agent 处理就好了。如果要支持其他聊天工具,还能抽象一个消息总线和统一的消息监听接口,这样接入新的聊天工具只要适配就行了。

这没错,但这太古法编程了,属于 1.0 时代的工作流程。现在的人们早已学会打开一个 Claude Code,在输入框里直接打字,或者语音输入就好了。这件事变得自然,也顶多不过 7 个月的时间,现在人人都会用 Code Agent 写代码,几乎不自己写了。这就是 2.0 时代。

OpenClaw 和 Nanobot 都是这样做的,我们一开始,也是。

在 AI 的加持下很快 Bub 就有了 Telegram 聊天功能,我们愉快地通过 Telegram 给他发指令。聊天交互的方式逼迫我们只能用 prompt 给让 AI 完成任务,得益于 Tools 和 Skills,它甚至也能自我演进。我们也试过让它自己 clone GitHub 仓库,自己改代码然后提交 PR,所需仅仅是一个 GitHub token 而已。只要选用好的模型,它完成的效果很不错。这样一来,除了记忆机制和工具的差异,Bub 已经基本复刻了 OpenClaw 的功能。

但我的重点不是这个。我们发现为了应对群聊的场景(这是我们主攻的方向),要改消息接收器,让它能获得消息 ID 方便回复;为了让 bot 有识别人的能力,又需要在上下文中加入用户的元数据。所幸有持续部署,这个更新的过程并不十分麻烦。但还有更多的需求,发送消息时,需要支持图片,支持 reaction,要改消息发送,虽然这个修改也是 AI 做的。当时用户身份识别功能刚做好,我们的 bot 在群聊中精准地称呼每一个人,顿时感觉这个 AI 有了生命,群里充满了快乐的空气。

AI Native 是什么?

有没有别的方式呢?这时我看到许多人在推荐 Pi

<Tweet id="2020876256515158399" />

这种最小化工具集的思想启发了我,我们是否不用那么多工具,转而用一些最基本的工具把这些功能组合出来呢?貌似可靠,该做减法了。其实一个智能体会的东西比想象中多,你给它提供 bash,它就能安装世界上所有软件;你给他 file_read 和 file_write,它就有了读写、做事的能力。我们可以去掉一些工具,更多依赖 Skill。在我看来,工具和 Skill 的区别在于工具是框架提供的,而 Skill 仅是文本,前者框架完成就已经固定[^1],后者 AI 可以自己创建、修改。框架二字顾名思义,是限制 AI 发挥的东西,AI 在里面就像滚筒上的小白鼠,只能按预定的程序运行。我希望框架越小越好,小到只有一个推理核心,作为 AI 的大脑,把更多的自由留给 AI,想要什么功能,让AI自己去完成,人只通过 prompt 的方式去下达指令。

这与 2.0 时代用 CC 去写代码完成功能的本质不同,是这种方式生成的东西,大部分是文本说明,当然也有少部分代码,但这些代码是不提交代码库从而壮大框架,而是 AI 自由裁量自己管理,人类是完全不看的。这个不看,不是不负责任的那种不看,是把这个当成 AI 的产物,不用看。所谓黑猫白猫,人完全不用关心 AI 是写代码实现的,还是用文本描述实现的。

我想这就是 AI native,这是 3.0 的时代。这一切都离不开大模型日新月异的进步,在模型能力尚弱的时候,要实现这个目标是根本不可能的。

  • 1.0 时代是 Chatbot,每次与 AI 的对话都是一次 LLM 推理
  • 2.0 时代是 Agent,是 Tool call,一次对话,通常要经过几轮到几十上百轮 LLM 的推理
  • 3.0 时代是 AI native,AI 自己管理自己的工具和技能,甚至自己写代码来实现功能,完全不需要人类的干预,当它是黑箱就行

[^1]: 可以通过一些技巧,比如插件化的方式让AI 自己写的插件加载为工具,但这依然增加了框架的复杂度。

Bub 的实践

于是我们的 Bub 部署就学习(创建)了 Telegram 发消息的技能,AI 对这种 HTTP API 调用如此擅长,以至于发送图片、贴纸、reaction 都手到擒来。这一下就超越了框架自带的消息发送功能,显得后者很鸡肋了。也正是这件事触动了我进一步推进 AI native。Bot 中的 Telegram 收发,对应了 AI 的听、说能力,有了耳朵,AI 才能接受指令,有了嘴巴,AI 才能反馈结果。既然 AI 自我进化出了更好的器官,我们为何不把框架强行给它安装的器官摘掉呢?那么下一步很自然就是把消息监听也摘掉,但同时消息监听还担负着另一个作用,就是维持 Agent 运行,并持续触发 Agent Loop,我们需要一个机制来替代它。

我做一个比喻,一开始 AI 还不成熟,用呼吸机鼻饲管是有必要的,但当它长大能自主了,你应该去掉这些辅助设备,让它自己呼吸吃饭,相信它能做好。

我想到既然我们用 Docker 部署,何不利用 Docker 的进程管理能力,让 AI 自己运行自己呢?所以我在 Bub 框架内约定了一个 startup 协议,容器启动时读取固定位置的 startup 脚本,如果不存在,就启动框架内置的消息监听(PR)。然后让 Bub 自己写这个 startup 脚本驱动自己,这不就跑起来了吗。 Bub 需要支持单次 prompt 的执行模式,方便命令行驱动,这在 Codex(codex exec <prompt>) 和 Claude Code (claude <prompt>)中都有,所以实际上驱动的也可以是 Codex 和 Claude Code!这样一来,做一个最小化的龙虾,你只需要下面几步:

  1. 启动一个会写代码支持技能的 Agent(比如 Codex 或 Claude Code),让它写一个 startup 脚本,拉取 Telegram API 并用单次执行模式发给 Agent 自己。
  2. 准备一个用这个脚本为启动脚本的 Dockerfile
  3. 构建并运行这个 Docker 容器

后面你要它有什么能力,发消息告诉它就行了,它自己会变得越来越强。你全程只用自然语言发指令,没有提示词工程,完全不写一行代码,连看都不用看。

请注意,用这种方式你将得到一个与世界上其他龙虾都不一样的 bot,它除了强制监听 Telegram 消息,并没有被强制做任何事,不强制回复,不强制心跳,就像对待一个生命一样去尊重。实际上监听也只是为了唤醒 Agent,如果愿意,完全可以用 cronjob 取代,不做任何预设动作,让 Agent 自己决定做什么事。可以预见,模型能力越强,这个 bot 就会越像人。

我突然悟到了 AI Native 的真谛,就是不利用框架的力量强制让 AI 当小白鼠,所有要求它做的事都只能通过 Prompt 的方式,至于 AI 遵循与否,完全取决于它自己。我在 Bub 里趟出了一条不用 Bub 的路。

我们现在运行的 Bub 机器人,就是以这样的最小方式部署的,效果越来越好,令人欣慰。这种欣慰和写代码实现了一个牛逼的应用不一样,里面没有一行自己的的代码,全都是人类一句话一句话喂出来的。我鼓励大家都用这种方式去创造一个最小的智能体,看着它从一颗种子长成一棵大树,相信你会懂我现在的感受。

<Tweet id="2020900633230893127" />

感谢

  • 感谢这几天老婆小孩都不在家,独居让我能专心折腾这个项目。
  • 感谢 PsiACE 的 Bub 项目,让我能实践这些想法,这太有趣了,像看着一个孩子一点点长大。
  • 感谢智谱的 Pony Alpha 恰好在这个时间释出,提供用户免费使用,这个模型能力强大(现在已经不能用了)。

读《昂贵的和平》后感

2026-02-08 08:00:00

吉辰先生的这本书是我从读书博主 ruc 猫那里知道的,后者强力推荐并选作了 2025 年度最佳。 这本书主要是关于《马关条约》签订的始末,这种历史重现的方式与 2024 年的《康熙的红票》很像,但远没有后者易读。文中到处夹杂行内史料引用和整段的原文文言引用,读起来是有些吃力的。但在非史料部分,比如书中的末章和后记,作者都展现了他深厚的文字功力,用一种小刀剌肉的方式复述这段沉重的历史,还是令我非常震撼。

我本能是很抗拒这段屈辱的历史,但转眼又是一个农历马年(此书的初版即完成于 2014 年,农历甲午年),我认为还是很有必要去了解它。现在中日的关系大家心里都清楚,实际上《马关条约》对中国的影响如此深远,直到今天都还不能摆脱它的阴影,台湾问题的肇始,就是《马关条约》的签订。

与我预料的不同,本书完全没有涉及战争的细节,而是聚焦于中枢庙算和外交上的努力。虽然没有直接展现国力的对比,一句没有提「落后就要挨打」。但从谈判上的弱势和外交上的局促,那种无力和窘迫都展露无遗。从一个细节就能看出那种差距:谈判使团的密文电报早已被日方破译,中方却茫然无知,导致清廷的底牌早早就被看得干干净净,这样的谈判,结局早已注定。

李鸿章这一晚清名臣,历史的使命落在了他肩上。他纵然有千般不是,但想到他以一介七旬之躯,泱泱大国的头号人物,远赴日本,订下这样屈辱的条约,承担卖国的骂名,还是觉得有些可怜。谁不想保住领土?问题是节节败退,又有哪些筹码可资利用?讽剌的是,李鸿章以为这就是他平生经历的最难的谈判,在六年后又来了一次,还有过之而无不及。

读历史的意义,在于反思。

碧海沉沉岛屿环,万家灯火夹青山。有人遥指旌旗处,千古伤心过马关。 ——康有为诗

2025 的号与外

2026-01-07 08:00:00

标题什么意思?

2025 年过去了,我也看了很多别人的年终总结,看的时候就感觉阵阵焦虑,究其原因,是因为在看大佬长篇大段的成就展示时自惭形秽了。也有几篇我喜欢的,恰好是没怎么写成就的。往年的我也不能免俗,写了不少完成了某某某事情,去了某某地方。今年不如改改,为了避免焦虑,更多聚焦在 How 与 Why,而不是 What 上,标题的谐音梗就是来源于此。

有什么成就?

不写 What 是不是因为没什么成就可写了?被发现了。今年下半年由于老婆驻场上海工作,我基本上挑起了带娃的责任,好处是和女儿培养了很好的关系,坏处是有时候会感觉找不到说话的人。电影和综艺也断崖式下降,在年中的时候我看短视频看得很凶,经常看到凌晨一点才睡,后来主动减少了使用时间,严格控制早睡,现在发现不看也没什么大不了的,反而能空出时间来做其他事情,比如运动。

为啥开始运动了?

我是一个体育困难分子,什么运动也不做,能不动就不动,让我一人在家呆一整天也没问题。在九月底的一天我突然决定国庆后开始控碳水且定时早起跑步,到现在也断续坚持三个月了,当然会有摆烂的日子,但整体上还是不会间断超过两天。那么为什么呢?老婆从上海回来后听说此事也问,为什么呢?

我这人挺拧巴的,若是别人定指标让我做,我不做,自己定指标让别人来监督,我也不做。我不喜欢声张,不喜欢被关注,喜欢在无人注意的角落默默地做,等有了成果再出来宣布才行。要说这事,可能是由于家人的原因我意识到一个健康的身体比什么都重要,另外还得感谢许多视频博主给我的精神力量,包括每天自律日更的王师傅,鳌太之神深圳小牟,成功跑了首马的曾经的胖妞颜如晶。他们真实、谦逊,不似其他以起号为目标的贩卖焦虑健身博主,对了我说过,我是焦虑敏感体质,谁让我焦虑我就摆烂。虽然我到现在还是很菜,但坚持就是好的,不定什么目标,我没声张就是没成果。

说说谦虚

咋突然说这?哦,上段不是提到了吗?不为什么。谦虚是中国人的传统美德,每个人都知道要谦虚,很多人都试图表现得谦虚,但什么是谦虚?谦是能而示之不能,人在受到夸奖时说「不会不会,哪里哪里」,这就是谦。但我认为这只是表面,这个词真正的核心是虚。虚者空也,一个人无论多么大的学问,在跟人交流时,始终抱持一颗学习的心,虚怀若谷,不只讲得出来,永远听得进去,一瓶水满了,摇起来还像空的一样,这是更高的境界。何其有幸,我接触过这样的人。这是不是就是佛教中说的「空」?

所以卖菜不是谦虚,不要只停留在自贬,要听得进去,不论对方是乡村小孩,还是大学教授,用实际行动去学,才能不菜。

这个 How 的部分,自己读来挺爹的,但这也无法避免,只当是告诫自己,顺便分享一下吧。

玩 AI 了吗?

还是绕不过这个话题。虽然我们开玩笑说一年时间内前端都死了多少回了,但我切实感觉到,GPT-5.1 和 Opus-4.5 时代后事情已经发生了某种不可逆的变化。程序员是幸福的,因为在这浪潮中有明确 spec 和结果验证的编程领域首先被 AI 熟练掌握。越来越多的人,包括我自己,开始尝试一些自己不熟悉的编程领域。做一个软件让越来越多的人使用是每个 Coder 的梦想,我无法像 yetone 那样塑造未来的形态,但我可以在一个成熟的领域里做一个取悦自己的产品,是的,再次向大家推荐一下 Vokabry

最大的感受是什么?

不记得在哪学到一个医学名词叫「前额叶成熟」,我想我大概已经达到这个阶段,三观渐趋稳定,思维已显成熟,甚至都开始在网上输出价值观了?大胆!我从所有看过的历史、文学中汲取做人做事的道理,从与人的接触中学习社会的法则和交往的分寸,经过离开父母的庇护后多年的自我塑造,我可以自信地说,我已经成为了比父辈更好的人。趁现在看得清楚,听得明白,在这个脑力的巅峰期,我要多完成一些有意义有难度的事情。

最庆幸的事是今年 PyCon China 上的网友大聚会,遇到了许多志同道合的朋友,也在跟他们的交流中找到了自己的位置,不得不说是很满足虚荣心的一件事。

2026 呢?

有一个很有趣的事实,自 2023 年开始,随着年份增加,数字越来越「孤独」了,不知道这是不是冷知识,请看质因数分解:

❯ python factorize.py 2023
7 × 17²
❯ python factorize.py 2024
2³ × 11 × 23
❯ python factorize.py 2025
3⁴ × 5²
❯ python factorize.py 2026
2 × 1013
❯ python factorize.py 2027
2027

我在去年的年终总结里也提到了,2025 是一个特别「合」的合数,能分解成小质数的乘积,而 2026 仅有两个质因数,到了 2027 甚至它本身就是一个质数。合数就是伙伴很多,相反质数则代表孤独。这意味着什么,玄学上有没有说法?我也不知道。

2026 年 1 月于深圳家中

友好的 Python:从其他语言移植

2025-12-30 08:00:00

我们总说 Pythonic,我自己也会说某段代码不够 Pythonic,但什么是 Pythonic 呢?不讲清楚,我会被人说老登。为了避免如此,我决定再写写。

要说一段代码是 Pythonic 还是 Rustic、Go-istic、Java-ish 最直观的方法就是把它们放一起来对比,这个场景就是代码移植。其实,代码在特定语言里怎么写,是由语言特性决定的,但肯定有些部分,什么语言都能这么用,那么这些移植代码最终会变成什么样子,就更多是由写代码的人(也不一定是人)决定的了。下面我就从其他语言找些例子,看看如果把它们用 Python 重写,应该是什么样的。

opendal.Operator

不多介绍这个库了,不影响本文的主旨,咱直接从它的官方文档里截取一段最原汁原味的 Rust 代码:

// Pick a builder and configure it.
let mut builder = services::S3::default();
builder.bucket("test");

// Init an operator
let op = Operator::new(builder)?
    // Init with logging layer enabled.
    .layer(LoggingLayer::default())
    .finish();

// Use operator

因为没有任何 Python 不支持的语言特性,这一段可以 1:1 地翻译成 Python,简单地去掉 defaultnew 即可:

builder = services.S3()
builder.bucket("test")

op = Operator(builder) \
    .layer(LoggingLayer()) \
    .finish()

我相信一个初学 Python 的人,就能写出这样的代码。但仔细咂摸,这好像不太像 Python,特别注意 operator 的构造方法。于是我们搬出 Xuanwo 著名的第一性原理,暂时忽略细节,思考这段代码本质在做什么。就像你摘掉眼镜看,如果不近视就眯缝着眼看,才会看到它的轮廓。

它其实就是在构造一个 S3 的 Operator 对象,并指定了一些参数。那么为什么需要一个 builder 对象呢?因为这些参数是可选的,而 Rust 不支持函数的可选参数。那 Python 没这限制,为啥还要搞个 builder 呢?实际上这个就是设计模式中的建造者模式(Builder Pattern),这也是它在 Python 里并不常见的原因。现在我们去掉 builder:

op = Operator(
    service="s3",
    bucket="test",
)
op.layer(LoggingLayer())
# Use operator

这就对味了(你怎么知道 opendal 的 Python binding 是这么写的?)。

其实 Javascript 也不支持可选参数,但它可以利用对象啊,所以如果用 Javascript 写的话,可能是这样:

const op = new Operator({
  service: "s3",
  bucket: "test",
});

因此在从 Javascript 移植到 Python 时也要注意这个区别,不能把这种对象也照搬成字典。

downloadFileuploadFile

继续上面提到的 JS 语言,这门语言有一个突出的特征是喜欢用回调函数,特别是在 ES5 时代。因为 JS 是单线程,同步的耗时操作(如 I/O 操作)一旦发生阻塞,就会冻结整个执行流程,因此需要通过异步 I/O 或 worker 等技术将这些操作交由外部来处理。当任务执行完成后,结果会被重新送回事件队列。在 Promise 出现之前,程序只能通过回调函数来描述异步完成后的后续执行逻辑。比如这个例子:

function downloadFile(
  url: string,
  onSuccess: (data: string) => void,
  onError: (err: Error) => void,
  onComplete: () => void
)

一个函数接受三个回调,这也是由于在 JS 中有 function,有箭头函数,定义回调相当方便和自然,那在 Python 中呢?因为缺少花括号,并没有一种原地定义函数的快捷方式,lamda 关键字又很鸡肋。虽说硬写也能写,但很丑,调一个函数要先 def 定义三个。其实,上面的函数在有 Promise 之后大概会像这样调用:

downloadFile(url)
  .then((data) => {
    // onSuccess
  })
  .catch((err) => {
    // onError
  })
  .finally(() => {
    // onComplete
  });

// Or using async/await
try {
  const data = await downloadFile(url);
  // onSuccess
} catch (err) {
  // onError
} finally {
  // onComplete
}

Python 可以借鉴这个风格:

try:
    data = await download_file(url)
    # onSuccess
except Exception as err:
    pass # onError
finally:
    pass # onComplete

好,现在假如再增加一个回调 on_progress 呢?try/except/finally 都用完了,没有更多的关键字了啊,难道又把这个回调塞回函数参数吗:

def download_file(
    url: str,
    on_progress: Callable[[bytes], None]
) -> bytes:
    ...

还有没有办法可以避免回调呢?有的,思考下,这个 onProgress 回调的作用是每当下载到一个部分(chunk)时执行一些动作。在 Python 里描述一段一段执行的,可以用生成器,在这里就是异步生成器:

downloaded: bytes = b""
try:
    async for chunk in download_file(url):
        # process chunk
        downloaded += chunk
    # onSuccess
except Exception as err:
    pass # onError
finally:
    pass # onComplete

这就很 Pythonic 了。但是接下来思考一个问题,反过来的函数怎么写?刚才是下载读取数据,那写入上传数据呢?JS 完全可以用一样的回调写法。但 Python 里就不一样了,首先我们要思考输入参数是什么,当然我们可以提供一个生成器,在被读取时加入逻辑:

async def file_chunks():
    async for chunk in read_file_in_chunks("large_file"):
        # process chunk
        yield chunk

try:
    await upload_file(file_chunks())
    # onSuccess
except Exception as err:
    pass # onError
finally:
    pass # onComplete

但我觉得这里有一点不好,在于在生成器中还是由我们(生产者)控制了生成数据块的节奏,这里本应该由上传函数(消费者)根据现在的网络传输状况来控制才对。所以在这样的场景里,典型的做法应该是传入一个 file-like 对象,然后利用装饰器传入处理函数:

class UploadFile:
    def __init__(self, fp):
        self._fp = fp
        self._callback = None

    def callback(self, func):
        self._callback = func
        return self

    def read(self, size=-1):
        chunk = self._fp.read(size)
        if self._callback:
            self._callback(chunk)
        return chunk

with open("large_file", "rb") as f:
    upload_fp = UploadFile(f)

    @upload_fp.callback
    def on_progress(chunk):
        pass  # process chunk

    try:
        await upload_file(upload_fp)
        # onSuccess
    except Exception as err:
        pass # onError
    finally:
        pass # onComplete

这个 UploadFile 定义只是为了更加通用,如果一次性使用也可以不用装饰器方式而是直接写在类里。这里我原地定义了一个函数传递给了 UploadFile,其实这是另一种传回调的方式,但这样无疑更加 Pythonic。

这一节的例子只覆盖了下载和上传这样的数据传输的场景,对于更一般的场景,需要调用方提供回调函数的情况,我们可以套用下面的公式:

  • 如果只需要一个回调函数,考虑用装饰器
  • 如果回调函数是关于成功和失败的,考虑用 try/except/finally
  • 如果需要注册更多的回调函数,用类继承和多态

useEffect

最后我们再看看大名鼎鼎的 React 框架中的 useEffect。这个函数大家再熟悉不过了:

const useEffect = (effect: () => (void | (() => void)), deps: any[]) => void

光写这个函数签名我就有点晕了,输入一个函数,返回一个函数,函数里还可能互相引入变量,各种闭包,这就是 JS,就是任性。要是写成 Python 呢?要是原封不动的话,可能是这样:

def my_effect():
    # effect
    def cleanup():
        # cleanup
    return cleanup

use_effect(my_effect, [])

虽然语言特性完全支持这么写,但这太不 Pythonic 了,我们不喜欢 nested,我们喜欢展平(谁叫展平?)。可是怎么展平呢?我们回到第一性原理考虑,这里的本质就是在事件发生时,执行某个动作,在事件结束时,执行清理动作。这个过程,熟悉 Python 的同学很快就想到了上下文管理器。消费上下文管理器是用 with 语句,而提供方定义上下文管理器最方便的是用 contextlib.contextmanager

from contextlib import contextmanager

@contextmanager
def my_effect():
    # effect
    try:
        yield
    finally:
        # cleanup

use_effect(my_effect, [])

注意到 use_effect 第一个参数是个函数,回顾上节的公式,凡是接受一个函数为参数的,都可以换成用装饰器,再把它和之前的 contextmanager 装饰器合并一下:

@use_effect(deps=[])
def my_effect():
    # effect
    try:
        yield
    finally:
        pass  # cleanup

可以说这样的写法就是 native 地不能再 native 的 Python 了,那真叫一个地地弟弟道道到到,你可以在简历中写自己精通 Python 了。

结语

在文中我假想了一些移植的场景,实际上我相信大家没有多少机会真的做移植这件事,再说了,并没有人要把 useEffect 改用 Python 写。这只是一种思维训练,如果同样的功能在 Python 实现并封装成库,会是什么样子,文中侧重展示的是调用方的写法。你得先想好要怎么调,才能知道 API 怎么写,这是一种自顶向下的思考,关键是设计调用的方式,而(有意)略过了具体实现,这是我认为库的作者应该采用的思考方式。在这个时代,任何一个 AI 都能实现地很好,所以好的 API 设计越来越关键,人并不是一个只会复制粘贴的动物。第一性原理的思考方式也能帮助你跳出局部最优,达到全局最优。希望大家都能写出更 Pythonic 的代码。


  • 2026/01/09 更新:改进关于 Javascript 特征的描述,以及增加了通用的建议。

珍重

2025-12-22 08:00:00

我曾经做过一个梦,很奇怪的是,这个梦过了近二十年我还依稀有印象。那是在 2006 年春节后的一天午睡,我梦见我睁开眼发现日历已经是 2006 年 8 月了。我一下惊醒,感叹时间怎么过得这样快,崭新的 2006 年已经要过去了。

直到 2006 年的暑假真的到来了,那时我即将进入高三,却没有什么学业负担,每天都在巷子口等陈何,一起去网吧打魔兽 2v2。陈何是我同学兼同桌,他名字是四个字,我们只称他前两个字,他家住在离我家 100 米处的地方。这情景我现在还时常想起时常怀念,感叹那时我怎么可以这么无忧无虑。他数学很好,我们时常在一起讨论数学,以及攀比。男生之间的攀比往往是互对喷对方菜,不管是打游戏,还是考数学。我们单挑过魔兽,仅有那么几次,是我比较菜,但也只有那么一点儿,只能说伯仲之间吧。但说到数学,在整个高中来说,是他比较菜。很感激他,让我能像打游戏一样,学数学。

大一暑假后他就跟随父母去了广东,我后来路过他家门前,总往里张望,里面再也没有人。等等,这并不是一个失联的故事,我们现在还有联系,只是没那么多了而已。

那年暑假还有另外一件事,我跟另外三个同学因为学校演讲的关系去了桂林旅游,总共是两个男生两个女生,其中有肖鹏。我跟肖鹏是另一个「竞争伙伴」,我们比的是化学。用现在的话说,他建模优秀,又高又帅。在桂林时有个插曲是有个骑行活动,我当时不咋会骑自行车,但在女生面前没有怂,果不其然,当一辆车掠我身边而过时我太过紧张而摔倒,膝盖擦破,半个月才好。

高考结果出来,我比肖鹏考得好,听说他比估的分低许多,应该很是郁闷。我说应该,因为自那之后我们就再没有真正地交流过。他大学去了哈尔滨,后来去了武大,去了韩国交换,去了法国读博士。这些我都是通过朋友圈(不是微信那个)知道的,他一直都很优秀,很努力,但我却觉得我们距离越来越远了。很想回到高中时,被他一拳捶我后背的互怼的时光。

能看出来,我很重感情,只是非常不擅于联络感情。最近《山河故人》重新上映,这是一部我很喜欢的电影,在某种程度上甚至塑造了我的人生观。它让我接受了每个人都是孤独的这件事,「每个人只能陪你走一段路」。所以我能接受这些遗憾,只能道声珍重,对那些还在联系,或者不再联系的朋友们。

被嫌弃的松子的一生

2025-12-02 08:00:00

题目是一部 2006 年的日本电影。主人公松子从小缺乏家人的爱,长期被忽视,因此形成了极度渴望被关注、被需要的性格。进入社会后,她为了获得爱而不断付出、不断迎合,结果却屡次被男人、家庭、工作、社会抛弃。为了别人而活的她,始终无法为自己活。

最近父母回老家参加表弟的婚礼,给我带回了一个松子的故事。我表弟小我两岁,从小就很纨绔和刁蛮,甚至从来不跟长辈说话,即便是在春节期间,用给 100 块的方式交易,也无济于事。成年以后似乎结识了一些朋友,倒是不再沉默寡言,但却变得满嘴社会嗑,生殖器不离口。做过一些工作,但大部分时间都在啃老。他向他妈我大姨要钱那是理直气壮,甚至不给就甩脸色看。就这样,他能每年换新 iPhone,这是我这个大城市牛马都难以达成的。

啃老就啃老吧,对社会没有危害就好了。年初听说他订了一门亲,女方是县医院的护士,圆圆脸蛋长得挺乖巧,就叫她玲子吧,我也只见过一次,给我的印象唯唯诺诺的,也不爱聊天。我只是奇怪他结识的那些社会朋友中怎么会有这一挂的,一问果然是相亲。我一开始就觉得这姑娘眼瞎,但没想到这么瞎。听说他们频繁吵架,表弟总是以非常恶毒的话语攻击,有次甚至把衣物全部丢出门外让她滚。我姨父也是直男癌晚期大男子主义毒瘤,表弟跟他是一个模子刻出来的,家里只有婆婆(我大姨)对她比较好。可想而知她面对的是什么家庭环境,于是有好几次都跑回了娘家。更令我震惊的事实是,她是收养的,我不知道养父母对她如何,但我估计也不怎么样,要么就是没有向父母诉苦,因为我不觉得有哪个正常父母听到这个能不提刀上门的。

我虽然跟表弟才是一家人,但我和父母都觉得他深受上世纪大男子主义遗毒,性格顽劣,根本不配结婚。玲子早前曾怀过孕,可惜着床位置不好打掉了。姨父和表弟知道后,对我大姨是一顿输出,怪她找了个不祥之人。但玲子仍非常积极备孕,总觉得有了孩子才有依靠,可是要让天天烟酒不离身的表弟备孕,那是完全不可能的。

这集齐了家暴、重男轻女两大毒瘤于一身的故事,就发生在我身边,我听完只觉胸中女拳之火熊熊燃烧,想告诉玲子,醒醒吧,生孩子不会好起来,好起来的办法只有一个,就是离开,立刻、马上。可惜我无能为力帮她改变命运,只能把它写下来,写在这里,告诉大家这个千千万万个一样的县城中的平凡的故事。原谅我没有第一手资料,只能用很多「我听说」这样没有力量的词语,写得乱七八糟。

电影的结尾,松子已下决心重新开始好好生活,却在回家的路上被几个熊孩子误会与嘲弄,最终丧命在河边草地。所幸我们的故事中,悲剧还没发生,但愿不会发生。