Logo

site iconJustYY | 小赖子

小赖子的英国生活和资讯,以及投资和个人生活。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

JustYY | 小赖子 RSS 预览

停机问题:悖论、自我指涉与计算机无法解决的问题

2026-10-18 01:53:35

停机问题(Halting Problem)是计算机科学中最著名的不可判定问题之一。本文从理发师悖论、说谎者悖论和罗素悖论出发,通过简单的 Python 代码和反证法,一步步解释图灵如何证明:不存在一个能够判断任意程序是否终止的通用算法,并探讨自我指涉、对角线论证以及人工智能的计算边界。

停机问题:悖论、自我指涉与计算机无法解决的问题

在计算机科学中,我们习惯于认为每个问题都有解决方案。有些问题很简单,有些问题很复杂,还有些问题需要极其庞大的计算资源。 但是,有没有这样一类问题:无论计算机性能多么强大,无论算法多么先进,都不可能找到一个能够解决所有情况的通用算法? 答案是:有。 其中最著名的例子之一,就是英国数学家艾伦·图灵(Alan Turing)在1936年研究的停机问题(Halting Problem)。 停机问题与数学中的反证法(Proof by Contradiction)密切相关,尤其涉及一种非常有趣的逻辑现象:自我指涉(Self-Reference)。 在理解为什么停机问题是不可判定的之前,我们先从几个经典的悖论说起。

一、什么是悖论(Paradox)?

悖论(Paradox)通常是指一种看似合理的陈述、规则或推理,却最终导致了逻辑矛盾的情况。 有些悖论源于自然语言的歧义,有些揭示了前提条件本身的不一致,还有一些则反映了数学和逻辑体系中的深层问题。 其中最有趣的一类悖论涉及自我指涉(Self-Reference),也就是一个陈述、规则或过程在某种意义上引用了它自己。 下面通过三个著名的例子来理解这个概念。

二、理发师悖论(Barber Paradox):谁给理发师刮胡子?

假设有一个小村庄,里面只有一位理发师。 这位理发师遵守一条非常严格的规则:
理发师给村子里所有不给自己刮胡子的男人刮胡子,而且只给这些男人刮胡子。
换句话说:
  • 如果一个男人不给自己刮胡子,那么理发师必须给他刮胡子。
  • 如果一个男人自己刮胡子,那么理发师就不能给他刮胡子。
听起来完全合理。 但是,现在我们问一个简单的问题: 理发师自己的胡子由谁来刮?

情况一:理发师给自己刮胡子

如果理发师给自己刮胡子,那么根据规则,理发师只能给那些不自己刮胡子的人刮胡子。 因此,他不能给自己刮胡子。 矛盾出现了!

情况二:理发师不给自己刮胡子

如果理发师不给自己刮胡子,那么按照规则,所有不给自己刮胡子的人都必须由理发师来刮胡子。 因此,理发师必须给自己刮胡子。 又出现了矛盾! 于是:
如果理发师给自己刮胡子,那么他就不能给自己刮胡子。 如果理发师不给自己刮胡子,那么他就必须给自己刮胡子。
无论选择哪一种情况,都会发生矛盾。 真正的结论并不是理发师不知道该怎么办,而是在这些规则之下,这样的理发师根本不可能存在。 问题出在规则的定义本身。 这就是著名的理发师悖论(Barber Paradox),它是罗素悖论的一种通俗表达。

三、说谎者悖论(Liar Paradox):这句话是假的

现在考虑下面这句话:
这句话是假的。
请问,这句话到底是真的还是假的?

假设这句话是真的

既然它是真的,那么它所表达的内容也必须是真的。 而它表达的内容是“这句话是假的”。 因此,这句话应该是假的。 矛盾!

假设这句话是假的

如果这句话是假的,那么“这句话是假的”这个陈述似乎恰好是正确的。 因此,这句话又应该是真的。 再次产生矛盾!
如果这句话是真的,那么它就是假的。 如果这句话是假的,那么它就是真的。
这个悖论的核心,在于一句话引用了自己的真假状态。 这就是说谎者悖论(Liar Paradox)。 它揭示了一个重要问题:当语言允许不受限制的自我指涉时,简单的真与假二元逻辑可能遇到困难。 不同的逻辑体系对此有不同的处理方式。例如,有些体系限制自我指涉的真值陈述,有些则允许比“真”和“假”更加复杂的真值状态。

四、罗素悖论(Russell's Paradox):一个不能包含自己的集合

英国数学家和哲学家伯特兰·罗素(Bertrand Russell)发现了一个与理发师悖论具有相同逻辑结构的问题。 假设我们可以根据任意条件构造集合。 现在定义一个特殊的集合 R:
R = { x | x 是一个集合,并且 x 不属于 x }
也就是说: R 包含所有不包含自身的集合。 例如,一个由数字组成的集合通常并不是它自己的元素,因此它不会包含自己。 现在提出一个问题: 集合 R 是否包含它自己?

情况一:R 包含自己

根据 R 的定义,它只能包含那些不包含自身的集合。 既然 R 包含自己,那么 R 就不应该包含自己。 矛盾!

情况二:R 不包含自己

根据 R 的定义,所有不包含自身的集合都应该属于 R。 既然 R 不包含自己,那么 R 就应该包含自己。 又出现了矛盾! 因此:
R 包含 R,当且仅当 R 不包含 R。
这就是著名的罗素悖论(Russell's Paradox)。 罗素悖论说明,不能不加限制地使用任意条件定义集合,否则会导致逻辑不一致。 它也是现代公理化集合论发展的重要推动因素之一,例如 Zermelo-Fraenkel 集合论(ZF/ZFC)就对集合的构造施加了严格限制。 现在回顾一下前面三个悖论:
  • 理发师悖论:理发师的规则最终应用到了理发师自己身上。
  • 说谎者悖论:一个句子引用了自己的真假状态。
  • 罗素悖论:一个集合的定义最终应用到了集合自己身上。
它们的共同点是什么? 一个规则最终作用于它自己,而这种自我指涉导致了矛盾。 理解这一点,对于理解停机问题非常重要。

五、停机问题(Halting Problem):能否预测一个程序会不会结束?

假设你正在开发一款静态代码分析工具。 用户输入任意一个程序以及该程序的输入数据。 你的工具需要判断:
这个程序最终会结束,还是会永远运行下去?
例如,考虑下面这段 Python 代码:

def program_a():
    print("Hello, World!")
显然,这个程序会结束。 再考虑:

def program_b():
    while True:
        pass
这个程序会无限循环,永远不会结束。 这两个例子非常简单,我们直接阅读代码就能判断结果。 但是,如果程序包含复杂的循环、递归、动态输入和条件判断呢? 有没有可能编写一个通用算法,能够判断任意程序在任意输入下是否最终停止运行? 这就是著名的停机问题(Halting Problem)。

形式化定义

假设我们存在一个函数:

halts(program, input)
它的返回值为:

True   # 程序最终会终止
False  # 程序会永远运行
我们要求这个函数满足三个条件:
  • 通用性:能够处理计算模型中所有合法的程序和输入。
  • 正确性:每次返回的结果都必须正确。
  • 终止性:halts 函数本身必须在有限时间内返回结果。
第三点尤其重要。 假设我们想判断一个程序是否永远循环。 如果只是执行这个程序,然后等待它结束,那么对于那些永远不会结束的程序,我们也只能永远等待下去。 所以,一个真正能够解决停机问题的算法,必须在有限时间内给出正确答案。 令人惊讶的是: 这样的通用算法在数学上不可能存在。 图灵在1936年的研究中证明了这一点。 下面我们来理解这个证明。

六、如何证明停机问题不可判定?

图灵的证明思路可以通过反证法(Proof by Contradiction)来理解。 反证法的基本思想是:
先假设我们想否定的结论是正确的。 然后根据这个假设进行推理。 如果最终产生不可避免的矛盾,那么原来的假设就是错误的。
对于停机问题,我们先假设存在一个完美的通用停机判断算法,然后构造一个特殊程序,使得这个算法无论返回什么结果都会出错。

第一步:假设存在完美的停机判断函数

假设存在:

def halts(program, input):
    # 如果 program(input) 最终停止,
    # 则返回 True。
    #
    # 如果 program(input) 永远运行,
    # 则返回 False。
    #
    # halts 自身总能在有限时间内结束。
    ...
我们不需要知道这个函数内部如何实现。 只需要假设: 无论输入什么程序,它都能在有限时间内给出完全正确的答案。

第二步:构造一个专门唱反调的程序

现在我们设计一个新的函数,名字叫 paradox(悖论)。

def paradox(program):
    if halts(program, program):
        while True:
            pass
    else:
        return
注意这个函数的逻辑:
  • 如果 halts 认为输入程序会停止,那么 paradox 就故意进入无限循环。
  • 如果 halts 认为输入程序不会停止,那么 paradox 就立即返回。
换句话说: paradox 故意采取与 halts 预测相反的行为。 再注意这里:

halts(program, program)
同一个程序既是被分析的程序,又作为它自己的输入。 这在理论计算模型中是允许的,因为程序本身可以被编码成数据,再传递给其他程序。 到目前为止,还没有出现矛盾。

第三步:让 paradox 分析它自己

现在执行最关键的一行:

paradox(paradox)
也就是说: 让 paradox 函数把自己作为参数传给自己。 此时,函数内部执行的判断变成:

halts(paradox, paradox)
这个调用询问的是:
当 paradox 以自己作为输入时,它最终会停止吗?
由于我们假设 halts 是完美的,因此它必须返回一个正确答案。 而答案只有两种可能:True 或 False。 下面分别分析。

情况一:halts(paradox, paradox) 返回 True

这意味着 halts 预测:
paradox(paradox) 最终会停止。
但是,根据 paradox 的实现:

if halts(program, program):
    while True:
        pass
一旦 halts 返回 True,paradox 就会进入无限循环。 也就是说:
halts 预测程序会停止。 但程序实际上永远不会停止。
因此,halts 的预测是错误的。 这与我们最初假设 halts 永远正确相矛盾!

情况二:halts(paradox, paradox) 返回 False

这意味着 halts 预测:
paradox(paradox) 永远不会停止。
但是,根据 paradox 的实现:

else:
    return
如果 halts 返回 False,那么 paradox 就会直接返回。 也就是说:
halts 预测程序永远不会停止。 但程序实际上立即停止了。
因此,halts 的预测再次出错! 同样与我们最初的假设矛盾。

第四步:得出结论

我们已经穷尽了 halts 可能返回的两种结果:
如果 halts 返回 True,paradox 就会无限循环,因此 halts 错了。 如果 halts 返回 False,paradox 就会立即停止,因此 halts 还是错了。
无论 halts 返回什么,都无法保证自己的预测正确。 然而,我们最初假设: halts 是一个对于任意程序都能够给出正确答案的通用算法。 现在这个假设产生了不可避免的矛盾。 因此,原来的假设不成立。 最终结论:
不存在一个总能在有限时间内结束,并且能够正确判断任意程序在任意输入下是否停止的通用算法。
这就证明了:停机问题是不可判定的(Undecidable)。

七、什么叫不可判定(Undecidable)?

在理论计算机科学中,判定问题(Decision Problem)是指答案只有“是”或者“否”的问题。 如果存在一个算法,能够对所有合法输入都在有限时间内给出正确答案,那么这个问题就是可判定的(Decidable)。 反之,如果不存在这样的算法,那么这个问题就是不可判定的(Undecidable)。 停机问题之所以不可判定,并不是因为所有程序都无法分析。 例如:

def count():
    for i in range(100):
        print(i)
我们显然知道它会结束。 对于某些特定程序,我们也可以通过数学归纳法、循环不变量、程序验证等方法,严格证明它们一定终止。 甚至有些无限循环,也能够很容易地识别出来。 真正不可能的是: 设计一个对所有任意程序和输入都有效、始终正确并且始终终止的通用判断算法。 这里需要特别区分三个概念:
  • 困难:问题可能需要大量时间或计算资源。
  • 计算复杂:问题存在算法,但算法的时间或空间复杂度可能很高。
  • 不可判定:根本不存在满足要求的通用判定算法。
不可判定并不意味着某个具体程序的行为无法判断,而是意味着不存在一个万能的算法。

八、停机问题本身真的是一个悖论吗?

严格来说,停机问题本身并不是一个逻辑悖论。 它是一个定义明确的数学问题,而它的不可判定性是一个经过严格证明的数学定理。 真正产生悖论式矛盾的地方,是我们假设存在一个完美的通用停机判断算法,然后利用自我指涉构造了一个反例。 这与理发师悖论有相似之处。 理发师悖论告诉我们:
不存在一位能够满足所述全部刮胡子规则的理发师。
停机问题的反证法告诉我们:
不存在一个能够满足全部通用性、正确性和终止性要求的停机判断算法。
不过,两者仍然存在技术上的区别。 理发师的定义直接导致逻辑不一致。 停机问题则通过构造一个特殊的程序,证明任何试图实现通用停机判断的可计算算法,都无法同时满足所有要求。 这种通过构造特殊对象来反驳某种“万能”假设的数学方法,称为对角线论证(Diagonalisation)。

九、停机问题与康托尔对角线论证

早在图灵之前,德国数学家格奥尔格·康托尔(Georg Cantor)就使用对角线论证,证明了实数集合是不可数的。 假设有人声称:
我已经列出了 0 到 1 之间所有的实数。
康托尔证明,我们可以沿着这个列表的对角线,逐位构造一个新的实数。 构造时,为每一位选择一个与对应列表数字不同的数字,并且避免小数表示中的歧义。 因此:
  • 新数字的第一位与列表中第一个数字不同。
  • 新数字的第二位与列表中第二个数字不同。
  • 新数字的第三位与列表中第三个数字不同。
  • 依此类推。
这样构造出的新数字必然与列表中的每一个数字至少有一位不同。 所以,它不可能出现在这个所谓完整的列表中。 由此产生矛盾: 原来的列表不可能包含所有实数。 图灵的停机问题证明采用了相似的思想。 康托尔构造了一个不在所谓完整列表中的数字。 图灵则构造了一个能够违背所谓万能停机判断器预测的程序。 两个证明的共同点都是: 通过精心构造一个特殊对象,揭示某个普遍性假设无法成立。

十、为什么不能直接运行程序来判断?

有人可能会问:
判断一个程序会不会结束,有什么困难?直接运行它不就知道了吗?
对于最终会结束的程序,这种方法确实有效。 但是,对于永远不会结束的程序呢? 假设我们运行一个程序,它已经执行了十亿次循环。 这能证明它永远不会结束吗? 当然不能。 它可能在第十亿零一次循环后结束。 也可能在一万亿次循环后结束。 还可能永远不会结束。 例如:

def very_slow_program():
    for i in range(10**100):
        pass
    return
这个程序需要执行非常多次循环。 但在理想化的计算模型中,它最终仍然会停止。 无论我们设置多长的超时时间,都不能保证正确区分所有最终会停止和永远不会停止的程序。 因此,单纯通过运行程序来判断,只能解决一部分问题。 如果程序最终结束,我们可以观察到它结束。 但如果程序一直没有结束,我们就无法仅凭等待判断它究竟是无限循环,还是尚未运行到终点。 这引出了一个重要概念: 停机问题是可识别的(Recognisable),但不是可判定的(Decidable)。 也就是说,我们可以构造一个过程,对所有最终会停止的程序,在有限时间内确认它们会停止。 但是,对于不会停止的程序,这个过程本身可能永远运行下去,而无法给出最终的否定答案。

十一、人工智能(AI)能解决停机问题吗?

随着人工智能的发展,尤其是大语言模型(LLM)、自动代码生成以及程序推理技术的进步,我们不禁会问: 如果未来的 AI 足够聪明,能不能解决停机问题? 答案仍然是: 不能,前提是这个 AI 本身仍然是普通的可计算算法,并且需要满足同样的通用性、正确性和终止性要求。 AI 的确能够帮助分析程序。 例如:
  • 识别常见的无限循环。
  • 分析递归函数的终止条件。
  • 推导循环不变量。
  • 辅助生成形式化终止证明。
  • 发现潜在的死循环或者逻辑错误。
但是,无论 AI 模型拥有多少参数、多少训练数据、多少 GPU 计算资源,都无法突破已经证明的可计算性边界。 这不是一个因为计算能力不足而暂时无法解决的问题。 这是一个在数学上已经被证明无法通过通用算法解决的问题。 当然,这并不意味着 AI 对程序验证没有价值。 现实世界中的绝大多数软件,并不需要面对理论计算模型中的全部可能程序。 我们仍然可以通过限制程序类型、添加前提条件或采用形式化验证等方法,让 AI 在实际软件工程中发挥巨大作用。

十二、从停机问题到莱斯定理(Rice's Theorem)

停机问题并不是一个孤立的数学现象。 它还引出了更广泛的不可判定性结果,其中最著名的就是莱斯定理(Rice's Theorem)。 通俗地说:
对于程序所计算的部分函数,任何非平凡的语义性质,在一般情况下都是不可判定的。
这里的“语义性质”,指的是程序实际计算什么,而不只是程序的源代码长什么样。 例如:
  • 任意给定程序所计算的函数,是否总是返回零?
  • 任意给定程序所计算的函数,是否对所有输入都有定义?
  • 任意给定程序所计算的函数,是否与某个参考程序计算完全相同的函数?
这些都是关于程序计算行为的非平凡性质。 莱斯定理告诉我们,对于图灵完备语言中的任意程序,不存在一个能够判断所有此类语义性质的通用算法。 不过,莱斯定理并不意味着有关程序的所有问题都不可判定。 例如:
一个 Python 文件是否包含某个特定的关键字?
这只是一个语法问题,而不是程序计算行为的语义问题。 这类问题完全可以通过算法解决。 因此,理解语法性质(Syntactic Property)与语义性质(Semantic Property)之间的区别非常重要。

十三、停机问题对软件工程意味着什么?

作为软件工程师,我们每天都在尝试分析和保证程序的正确性、稳定性、性能以及安全性。 停机问题告诉我们:对于任意程序,完全自动化且永远正确的通用分析,在理论上存在不可逾越的限制。 我们不可能构造一个万能静态分析器,使其在所有情况下都能在有限时间内准确预测任意程序的全部运行行为。 因此,现实中的编译器、静态分析器、形式化验证工具通常需要进行取舍。 例如:
  • 限制输入范围:只分析某些特定类型的程序。
  • 允许未知结果:当无法证明某种性质时,返回 Unknown。
  • 采用保守近似:宁可报告一些实际上不会发生的问题,也尽量避免漏掉真正的问题。
  • 要求额外信息:让开发者提供循环不变量、函数契约、终止性度量或者辅助证明。
这些限制并不意味着工具设计得不好。 恰恰相反,它们往往反映了工程师对计算理论边界的正确理解。 一个优秀的软件工程师,不仅需要知道如何解决问题,还需要知道哪些问题根本不可能被某种通用方法解决。

十四、总结:计算能力也有数学上的边界

理发师悖论、说谎者悖论、罗素悖论以及停机问题,都展示了自我指涉在逻辑和数学中的重要作用。 理发师悖论告诉我们,一个看似合理的规则可能根本无法被满足。 说谎者悖论展示了语言中自我指涉带来的真假矛盾。 罗素悖论揭示了为什么不能不受限制地定义集合。 而图灵的停机问题,则将这种思想带入了算法与计算的世界。 它证明了一个令人震撼的结论: 并不是所有定义清楚的问题,都存在能够解决所有情况的算法。 更重要的是,这个结论并不局限于 Python、C++ 或者某一种计算机架构。 对于任何具有图灵完备计算能力的编程语言,停机问题的不可判定性都成立。 即使我们拥有一台速度无限快的理想计算机,也不能因此让停机问题变成可判定问题。 因为这里的困难并不是计算速度不够,而是满足条件的通用算法根本不存在。 整个证明的核心,可以归纳为一句话:
如果存在一个能够正确预测任意程序是否停止的万能算法,那么我们就可以构造一个专门与其预测结果相反的程序,从而推翻这个万能算法存在的假设。
这就是停机问题不可判定的根本原因。 有时候,计算机科学中最深刻的发现,并不是设计出一个更快、更聪明的算法。 而是通过严密的数学证明,告诉我们:某些我们一直试图寻找的算法,根本不可能存在。

参考资料

[show_posts keyword="悖论"] 英文:Teaching Kids Programming - The Halting Problem: Paradoxes, Self-Reference, and Why Some Problems Are Impossible to Solve

用 iPhone 自带快捷指令把照片切成九宫格: 从零创建、下载、安装及 Python 实现

2026-09-21 22:28:37

iPhone 自带的「快捷指令」其实就能实现九宫格切图,不需要额外安装 App。本文记录如何从零创建一个“图片九宫格”快捷指令,让它支持从相册或共享菜单接收图片,并按照原图尺寸直接分割为 3×3 九张图片,同时介绍 .shortcut 文件、iCloud 分享链接,以及对应的 Python 实现和完整命令流程。
最近折腾了一下 iPhone 自带的「快捷指令(Shortcuts)」,做了一个挺实用的小工具:把一张照片自动切成 3×3 的九张小图,也就是我们平时所说的九宫格切图。 以前碰到这种需求,第一反应一般都是去 App Store 找个九宫格 App,或者找一个在线网站,把照片上传以后再切割。其实完全没有必要,iPhone 自带的「快捷指令」就能完成。 而且做好一次以后,以后的操作基本就是: 选择一张照片 → 运行快捷指令 → 自动得到九张图片。 不用安装第三方 App,也不用把照片上传到什么网站。

直接下载 / 安装「图片九宫格」快捷指令

如果不想自己从头做,可以直接安装我已经做好的版本: 在 iPhone 或 iPad 上打开上面的链接,会进入 Apple 的 Shortcuts 页面,然后选择添加快捷指令即可。 安装完成以后,「图片九宫格」就会出现在自己的快捷指令列表里。 [caption id="attachment_73032" align="alignnone" width="1905"]iPhone/iOS九宫格快捷方式/代码 iPhone/iOS九宫格快捷方式/代码[/caption]

也可以直接分享 .shortcut 文件

iOS 的快捷指令实际上也可以导出成一个扩展名为:
.shortcut
的文件。 所以除了上面的 iCloud Link,我也可以把导出的:
图片九宫格.shortcut
放到自己的服务器上供人下载。 不过如果是公开放在博客上,我个人更推荐使用 Apple 的 iCloud Shortcut Link。 一方面安装流程更自然,点击以后直接进入 Apple 的 Shortcuts 页面;另一方面 Apple 会处理快捷指令的签名和验证。

.shortcut 到底是什么文件?

这个问题还挺有意思。 一开始我还以为 .shortcut 可能就是一个 XML、JSON 或者普通的 Property List。 实际上,现在从 iPhone 导出的 .shortcut 文件要复杂不少。 我看了一下这次导出的文件,它的开头是:
AEA1
它实际上是一个由 Apple 签名的 Archive。 简化之后,大概可以理解成:
.shortcut
│
└── AEA1 Signed Archive
    │
    └── Apple Archive
        │
        └── Shortcut.wflow
            │
            └── Binary Property List
                │
                ├── Workflow 信息
                ├── Shortcut 图标
                ├── 输入输出类型
                └── WFWorkflowActions
                    └── 真正的一条条快捷指令命令
我这次导出来的文件大概只有 26 KB,但是里面已经包含了一条完整的 Workflow,以及 Apple 的 Signing Certificate Chain。 这也是为什么不建议直接拿 Hex Editor 或者文本编辑器去修改 .shortcut 文件。 哪怕里面真正的 Workflow 数据可以被解析出来,直接修改导出的签名文件也会破坏签名。

这个快捷指令到底做了什么?

最终目的非常简单。 假设有这么一张照片:

┌─────────────────┐
│                 │
│                 │
│     原始照片     │
│                 │
│                 │
└─────────────────┘
直接按照原图的宽和高分别三等分,切成:

┌─────┬─────┬─────┐
│  1  │  2  │  3  │
├─────┼─────┼─────┤
│  4  │  5  │  6  │
├─────┼─────┼─────┤
│  7  │  8  │  9  │
└─────┴─────┴─────┘
这里有一个容易误解的地方: 3×3 九宫格并不代表每一张小图都必须是正方形。 它只是把原始图片平均分成 3 行、3 列。 例如原图是:
4032 × 3024
那么每一块大约就是:
1344 × 1008
原图的宽高比不会被改变,也不需要事先从中间裁成正方形,因此不会丢掉原图左右或者上下的内容。 最后把这九张照片全部保存到相册。

从零开始创建「图片九宫格」

下面再记录一下如果完全从零开始,该怎么做。

第一步:创建 Shortcut

打开:
快捷指令
→ 所有快捷指令
→ 右上角 +
创建一个新的快捷指令。 我给它取名:
图片九宫格
名字当然可以随便取。

第二步:支持两种图片输入方式

我希望这个 Shortcut 有两种使用方法。 第一种是:
打开快捷指令
→ 点击“图片九宫格”
→ 选择照片
第二种更方便:
照片
→ 选择一张图片
→ 分享
→ 图片九宫格
因此首先把 Shortcut 设置成可以从: 共享表单 / Share Sheet 接收: 图像 / Images 然后判断到底有没有传进来照片。 快捷指令前面的逻辑就是:
从共享表单接收 图像

如果“快捷指令输入”有任何值

    将变量 img 设置为“快捷指令输入”

否则

    选择照片

    将变量 img 设置为“照片”

结束如果
这其实就是我们写程序时最普通的:
if shortcut_input:
    img = shortcut_input
else:
    img = select_photo()
这样后面的 Workflow 就不用关心图片到底是从哪里来的。 统一处理:
img
就行了。

第三步:获得图片 Width 和 Height

接下来添加: 获取图像详细信息 / Get Details of Images 分别获取:
Width
Height
例如一张 iPhone 照片可能是:
4032 × 3024
这里不需要把图片裁成正方形。 九宫格只是把原图横向分成三列、纵向分成三行,所以应该分别计算:
tileWidth = Width / 3
tileHeight = Height / 3
例如:
Width = 4032
Height = 3024

tileWidth = 4032 / 3
          = 1344

tileHeight = 3024 / 3
           = 1008
所以每张小图片的尺寸就是:
1344 × 1008
这样能够完整保留原图。 而且因为 Width 和 Height 都同时缩小为原来的三分之一,每张小图片和原图的宽高比完全相同。

第四步:计算九张图片的位置

图片坐标从左上角开始:
(0, 0)
X 向右增加,Y 向下增加。 现在我们有两个尺寸变量:
tileWidth
tileHeight
所以九张图片的位置是:
图片 X Y
1 0 0
2 tileWidth 0
3 2 × tileWidth 0
4 0 tileHeight
5 tileWidth tileHeight
6 2 × tileWidth tileHeight
7 0 2 × tileHeight
8 tileWidth 2 × tileHeight
9 2 × tileWidth 2 × tileHeight
然后使用: 裁剪图像 / Crop Image 选择:
Position = Custom
Width 设置成:
tileWidth
Height 设置成:
tileHeight
X 和 Y 使用上面计算出来的坐标。

更漂亮一点:用两个 Repeat 循环

当然,我们完全可以真的创建九个 Crop Image。 但是从程序员角度看,这显然有点重复。 更漂亮的做法是两个循环:
for row in range(3):
    for col in range(3):
        x = col * tileWidth
        y = row * tileHeight

        crop(
            x=x,
            y=y,
            width=tileWidth,
            height=tileHeight
        )
Shortcut 里面也可以使用: 重复 / Repeat 来实现。 外层:
重复 3 次
代表 Row。 先计算:
row = Repeat Index - 1
然后里面再:
重复 3 次
代表 Column。 计算:
column = Repeat Index - 1

X = column × tileWidth
Y = row × tileHeight
然后:
裁剪 img

Position = Custom
X = X
Y = Y
Width = tileWidth
Height = tileHeight
每次得到的结果加入变量:
gridImages
两个循环完成以后:
gridImages
里面正好就是九张照片。

第五步:保存九张照片

最后增加: 存储到照片相簿 / Save to Photo Album 输入:
gridImages
Shortcut 就完成了。

完整的 Shortcut 命令流程

如果把整个快捷指令按照命令顺序展开,大概就是:
1. 从共享表单接收“图像”


2. 如果 Shortcut Input 有任何值

       设置变量 img = Shortcut Input

   否则

       选择照片
       设置变量 img = 照片

   结束如果


3. 获取 img 的 Width

       → width


4. 获取 img 的 Height

       → height


5. 计算

       tileWidth = width / 3


6. 计算

       tileHeight = height / 3


7. 重复 3 次

       row = Repeat Index - 1

       重复 3 次

           column = Repeat Index - 1

           X = column × tileWidth
           Y = row × tileHeight

           裁剪 img

               Position = Custom
               X Coordinate = X
               Y Coordinate = Y
               Width = tileWidth
               Height = tileHeight

           将裁剪结果加入 gridImages

       结束重复

   结束重复


8. 将 gridImages 保存到照片相簿
如果从软件工程角度看,这其实已经是一个非常标准的小程序了。 有:
  • Input
  • Variable
  • If / Else
  • Nested Loop
  • Arithmetic
  • Coordinate Calculation
  • Image Processing
  • Output
只不过 Apple 把这些程序设计概念全部变成了一个个可以拖动的 Action。

如果用 Python 写,会是什么样?

其实 Shortcut 背后的核心算法用 Python 写出来反而更容易理解。 安装 Pillow:
pip install pillow
然后可以写成:
from pathlib import Path
from PIL import Image, ImageOps


def make_nine_grid(input_file: str, output_dir: str = "nine_grid"):
    input_path = Path(input_file)
    output_path = Path(output_dir)

    output_path.mkdir(parents=True, exist_ok=True)

    # 打开图片
    image = Image.open(input_path)

    # 根据 EXIF 信息纠正手机照片的方向
    image = ImageOps.exif_transpose(image)

    width, height = image.size

    # -------------------------
    # 第一步:计算 3 × 3 的边界
    # -------------------------
    #
    # 宽和高分别三等分。
    #
    # 不需要先把原图裁成正方形,
    # 因此不会丢失原始图片的内容。
    #
    # 使用边界而不是简单的 width // 3
    # 和 height // 3,可以处理尺寸不能被
    # 3 整除的情况。
    #

    x = [
        0,
        width // 3,
        width * 2 // 3,
        width,
    ]

    y = [
        0,
        height // 3,
        height * 2 // 3,
        height,
    ]

    # -------------------------
    # 第二步:裁成九张图片
    # -------------------------

    index = 1

    for row in range(3):
        for col in range(3):

            box = (
                x[col],
                y[row],
                x[col + 1],
                y[row + 1],
            )

            tile = image.crop(box)

            filename = output_path / f"grid_{index:02}.png"

            tile.save(filename)

            print(
                f"{index}: "
                f"row={row}, "
                f"col={col}, "
                f"box={box}, "
                f"size={tile.size}"
            )

            index += 1


if __name__ == "__main__":
    make_nine_grid(
        "photo.jpg",
        "nine_grid",
    )
运行:
python nine_grid.py
最后目录里面就会得到:
nine_grid/
├── grid_01.png
├── grid_02.png
├── grid_03.png
├── grid_04.png
├── grid_05.png
├── grid_06.png
├── grid_07.png
├── grid_08.png
└── grid_09.png

Python 版本还有一个值得注意的小细节

假设图片尺寸刚好可以被 3 整除,例如:
4032 × 3024
那么:
4032 / 3 = 1344
3024 / 3 = 1008
最终九张图片都会是:
1344 × 1008
但是并不是所有图片的尺寸都一定可以被 3 整除。 比如原图是:
4033 × 3025
那么:
4033 / 3 = 1344.333...
3025 / 3 = 1008.333...
图片当然不可能包含:
0.333 Pixel
如果简单写:
tileWidth = width // 3
tileHeight = height // 3
得到:
tileWidth = 1344
tileHeight = 1008
那么三个 tile 加起来只有:
1344 × 3 = 4032

1008 × 3 = 3024
这样原图右边和下面都会各剩下一个 Pixel。 所以 Python 版本没有简单地固定每块尺寸,而是直接计算四条 X 边界:
x0 = 0
x1 = width // 3
x2 = width * 2 // 3
x3 = width
以及四条 Y 边界:
y0 = 0
y1 = height // 3
y2 = height * 2 // 3
y3 = height
对于:
width = 4033
X 坐标就是:
0
1344
2688
4033
三个横向区间分别是:
0    → 1344
1344 → 2688
2688 → 4033
最后一块可能会比前面的多一个 Pixel,但整个原图的所有 Pixel 都会被保留下来。 Height 也是同样的处理方式。 这种做法的关键不是要求九张图片绝对一模一样大,而是: 保证整张原始图片被完整、连续、无重叠地切成 3×3 九个区域。

为什么不用先裁成正方形?

这里其实很容易产生一个误解: “九宫格”并不等于“九张正方形图片”。 3×3 只是说:
3 Columns × 3 Rows
如果原图是:
4032 × 3024
它的宽高比是:
4 : 3
切成 3×3 以后,每一块大约是:
1344 × 1008
宽高比仍然是:
4 : 3
这其实正是我们想要的。 因为横向和纵向都缩小了三倍,所以每一块和原图拥有相同的 Aspect Ratio。 如果为了所谓的“九宫格”先从中间把:
4032 × 3024
裁成:
3024 × 3024
那么左右两边一共会直接丢掉:
4032 - 3024
= 1008 Pixels
对于照片来说,这可能会把原本位于画面边缘的人物或者景物直接裁掉。 所以正确的做法就是: 保持原始照片尺寸,Width 和 Height 分别三等分。

平时怎么使用?

做好以后其实基本不用再打开 Shortcut 编辑器。 最简单的方式:
快捷指令
→ 图片九宫格
→ 选择照片
然后九张照片就会保存到相册。 我自己更喜欢从 Photos 直接运行:
照片
→ 打开一张图片
→ 分享
→ 图片九宫格
这时候照片会直接作为:
Shortcut Input
传进去。 因此连再次选择照片都不需要。

为什么我更喜欢 Shortcut 而不是九宫格 App?

当然 App Store 上肯定有大量九宫格工具。 但是这么简单的需求再安装一个 App,我觉得有点多余。 自己做 Shortcut 有几个好处。 首先不用看广告,也没有什么 VIP、会员或者每天免费处理三张这种限制。 其次照片不需要上传到什么未知的网站。 最重要的是,做完以后这是一个完全属于自己的小工具。 而且 Shortcut 很容易继续修改。 比如把:
3 × 3
改成:
2 × 2
就是四宫格。 改成:
4 × 4
就是十六宫格。 甚至可以在程序开始的时候:
询问输入

请输入 N:
然后分别计算:
tileWidth = width / N
tileHeight = height / N
循环也改成:
for row in range(N):
    for col in range(N):
如果用 Python 写得更加严谨一点,则依然可以直接计算所有边界:
x[i] = width * i // N
y[i] = height * i // N
其中:
i = 0, 1, 2, ..., N
这样就可以保证无论原始图片尺寸能不能被 N 整除,所有 Pixel 都会被完整使用。 这样它就不再是一个单纯的九宫格 Shortcut,而是一个: N × N 通用图片切割工具。

Shortcuts 本质上也是编程

以前我一直觉得 iPhone 的 Shortcuts 只是一个简单的自动化工具。 真正自己做几个以后,会发现它实际上和写程序没有什么本质区别。 比如这次的九宫格,里面已经有:
Input
Variable
If / Else
Loop
Nested Loop
Arithmetic
Coordinate
Image Processing
Output
甚至还涉及到一个很典型的二维坐标问题。 核心算法实际上就是:
for row in range(3):
    for col in range(3):

        left   = width * col // 3
        right  = width * (col + 1) // 3

        top    = height * row // 3
        bottom = height * (row + 1) // 3

        crop(
            left,
            top,
            right,
            bottom,
        )
用 Python 写可能几十行代码就解决了,而 Shortcuts 的好处是: 写完以后直接变成了 iPhone 上随时可以使用的小工具。 以后想切九宫格的时候,只需要:
照片
→ 分享
→ 图片九宫格
几秒钟以后,九张图片就已经在相册里面了。 有些很小的需求,其实根本没必要专门安装一个 App。 iPhone 自己已经提供了一套相当不错的自动化和编程环境。 [show_file file="/var/www/wp-post-common/justyy.com/ios.php"] [show_posts keyword="iphone"] 对了,我Vibe-code了一个把图片切割成格子的工具:图片切割:将图片精确划分为网格,预览每个分块并下载一个 ZIP 文件。图片始终保留在此设备上。 英文:Split a Photo into a 3×3 Grid with iPhone Shortcuts: Build, Install, Share and Recreate It in Python

微软 Global Hackathon 2026: 一周时间, 把一个想法先做出来

2026-09-20 21:00:50

这两年开始参加微软每年九月份举办的 Global Hackathon。今年我也注册并加入了两个 Project。Hackathon 通常只有一周时间,最终交付往往只是一个两分钟左右的 Demo 视频,因此相比成熟产品,Idea 和 Proof of Concept 更重要。对我来说,这更像是一周难得的“合法折腾时间”:可以看看其他团队的想法、学习新技术,也可以暂时跳出日常项目的节奏,快速验证一个平时未必有机会尝试的 Idea。
[caption id="attachment_73016" align="alignnone" width="1241"]注册Hackathon就有电子徽章 注册Hackathon就有电子徽章[/caption] 微软的 Hackathon 是每年九月份举办的一项内部创新活动,最早从 2014 年开始,到现在已经举办十多年了。规模也非常大,参与者往往来自全球不同的团队、不同的产品线,很多平时完全没有机会一起合作的人,会因为一个 Idea 临时组成一个 Team。 以前我一直知道公司有这么一个活动,不过真正开始参与,也就是最近这两年的事情。

Hackathon 通常只有一周

Hackathon 正式活动基本就是一周。 一般第二周的周一,需要上传一个大约两分钟的视频,介绍这个 Project 是做什么的、解决了什么问题,以及最终的 Demo。 有意思的是,Hackathon 并不像正式的软件项目一样要求提交一个非常完整的产品。 代码当然可以写,但通常不会有人真正去逐行 Review 你的代码。 很多时候,最终真正的 Output 或 Delivery,其实就是那个两分钟的视频。 所以参加过以后,我越来越觉得,Hackathon 和平时做软件工程项目的思维方式其实很不一样。 平时的软件开发,我们会考虑很多东西: Architecture、Code Quality、Testing、Security、Performance、Deployment、Maintainability。 但 Hackathon 只有短短一周。 从星期一开始做到星期五,真正能够写代码和折腾的时间其实并没有多少。 想在几天时间里做出一个成熟、稳定、Production Ready 的产品,基本不太现实。 所以大部分 Hackathon Project,本质上都只是: Proof of Concept。 也就是先证明: 这个 Idea 是可行的。

Fake it until you make it

因为时间很短,所以 Hackathon 里其实很适合一种思路: Fake it until you make it。 当然,这里的 Fake 并不是伪造结果。 而是说,为了快速验证一个 Idea,可以先 Mock 一些数据,可以把一些复杂流程简化,也可以先做一个看起来完整、实际上只实现核心功能的 Prototype。 比如一个真正的产品可能需要: Backend、Database、Authentication、Security Review、Monitoring、Deployment、Scalability…… 但 Hackathon 的时候,很多东西完全可以先不做。 只要最后能够通过一个 Demo,让别人理解: 如果我们真的把这个东西做出来,它最终可能是什么样子? 其实就已经达到目的了。

Idea 有时候比 Delivery 更重要

所以我觉得 Hackathon 最有意思的一点,就是: Project Idea 往往比最终的 Delivery 更重要。 正常工作里面,一个新的 Idea 想真正进入 Product Roadmap,需要考虑很多现实问题: Priority、Resource、Budget、Customer Requirement、Security、Compliance、Cost…… 有时候一个想法讨论了几个月,可能连第一行代码都还没开始写。 Hackathon 就不一样。 有一个 Idea? 先做出来看看。 先用几天时间验证一下。 如果最后发现这个 Idea 不靠谱,也没有什么关系。 至少大家验证过了。 但有时候,也会发现一些原本看起来有点异想天开的想法,在做成 Demo 以后,居然真的挺有意思。 这其实就是 Hackathon 最大的价值之一。

今年我 Join 了两个 Project

今年我也注册参加了 Hackathon,而且 Join 了两个 Project。 不过严格来说,我并没有 100% 全职投入到 Hackathon 里面。 更多的时候属于哪里需要帮一点忙,就过去帮一下。 Hackathon 其实挺自由的。 微软的LOGO在衣服后面脖胫处。参加的第一天/周一就有工程师穿上了。 [caption id="attachment_73011" align="alignnone" width="2048"]今天微软哈克松领的东西挺多的。 今天微软哈克松领的东西挺多的。[/caption] 注册了,并不一定真的要参加。 参加了,也不一定要上传视频。 上传了视频,也不一定会获奖。 并不是说报名以后,这一周就必须完全停止正常工作,然后每天全职 Hack。 参与多少,其实完全取决于自己的时间和兴趣。 我今年也就是参与一下,有需要的时候帮忙做一点东西。

注册以后还有衣服、SWAG 和午餐券

Hackathon 还有一些比较有意思的周边。 注册以后,星期一早上通常可以去领取 Hackathon 的衣服,还有一些小的 SWAG。 今年还发了一张免费的午餐券。 其实这些东西本身值不了多少钱,但会让整个活动稍微多一点仪式感。 公司里平时大家都在忙自己的 Project。 到了 Hackathon Week,办公室里面会明显感觉到有一点不一样。 会看到一些平时没见过的 Team 在做一些奇奇怪怪的东西。 有人做 AI。 有人做 Hardware。 有人做 Developer Tool。 也有人做一些非常 Experimental 的 Project。

不一定要获奖

我觉得参加 Hackathon 最容易陷入的一个误区,就是把它理解成一个比赛。 当然,它确实有评选,也有各种获奖 Project。 但对于绝大多数参与者来说,获奖其实并不是最重要的事情。 我反而觉得: 只要参与了,就已经有价值。 注册了,是好事。 Join 了一个 Project,也是好事。 真正写了几天代码,更好。 最后还能录一个两分钟的视频,那就更完整了。 至于最后有没有拿奖,反而没有那么重要。 [caption id="attachment_73008" align="alignnone" width="1152"]哈克松的本子,都不舍得拿来写。 哈克松的本子,都不舍得拿来写。[/caption]

一周“合法的折腾时间”

对我来说,Hackathon 越来越像是一周: 合法的折腾时间。 平时大家都有自己的 Project、Deadline、Bug、Meeting 和各种正常工作。 很难专门拿几天时间,去尝试一个和自己日常工作没有直接关系的东西。 Hackathon 正好提供了这么一个机会。 这一周正常工作的节奏一般会稍微轻一点。 可以看看公司其他团队最近有什么新的 Idea。 也可以 Join 一个以前完全不了解的 Project。 甚至只是利用这一周学习一个新的 Framework、一种新的 AI 技术,或者认识几个平时完全没有合作机会的同事,也挺有价值。 有时候最重要的甚至不是最后做出了什么。 而是: 你终于有理由花几天时间去研究一个平时根本不会去碰的东西。

Idea 比结果更重要

所以我现在看 Hackathon,已经没有以前那么在意最后到底做出来一个什么东西了。 一周时间,本来就不可能做出特别成熟的产品。 真正重要的是: 有没有一个值得尝试的 Idea。 有没有通过 Prototype 验证它。 有没有在这个过程中学到一点新东西。 平时的软件工程更多是在回答: 这个东西怎么才能可靠地做出来? 而 Hackathon 更像是在问: 这个东西,为什么不能先试试看? 我觉得这可能才是 Hackathon 最有意思的地方。 [caption id="attachment_73010" align="alignnone" width="2048"]免费午餐 ,5.1英镑, 那天我才了解到,可以花1.5倍的价格获得大概双份的量。公司的饮料/水果是免费的,这个午餐券还包括一个甜点。 免费午餐 ,5.1英镑, 那天我才了解到,可以花1.5倍的价格获得大概双份的量。公司的饮料/水果是免费的,这个午餐券还包括一个甜点。[/caption] [caption id="attachment_73009" align="alignnone" width="2048"]免费的午餐 最香了。 免费的午餐 最香了。[/caption] 这两年,我才开始比较认真地参加微软每年一度的 Global Hackathon。 去年获得了一个奖:拿到了一个纪念杯子:这周第一次参加微软的Hackathon/黑客马拉松 [show_file file="/var/www/wp-post-common/justyy.com/microsoft.php"] [show_posts keyword="Hackathon"] 英文:Microsoft Global Hackathon 2026: One Week to Turn an Idea into Something Real

CP/M: 那个比 MS-DOS 更早的操作系统

2026-09-19 19:31:33

[bctt tweet="知道DOS甚至CP/M的都暴露年龄了。"]

回顾 CP/M——一个诞生于微软 DOS 之前、并深刻影响早期个人电脑发展的经典操作系统。从 DIR、TYPE、FORMAT 等熟悉命令中,可以看到 DOS 时代乃至后来 PC 操作系统发展的许多早期痕迹。 CP/M:微软 DOS 之前的操作系统先驱 CP/M: The Operating System Pioneer Before Microsoft DOS 在 DOS 之前:回顾 CP/M 操作系统 Before DOS: A Look Back at the CP/M Operating System CP/M:那个比 MS-DOS 更早的操作系统 CP/M: The Operating System That Came Before MS-DOS 从 CP/M 到 DOS:个人电脑操作系统的早期历史 From CP/M to DOS: The Early History of Personal Computer Operating Systems 看到 CP/M,仿佛看到了 DOS 的祖先 Looking at CP/M Feels Like Seeing an Ancestor of DOS
这是 CP/M,一个比微软 DOS 还要早很多年的操作系统。 从今天的角度来看,它的命令行界面其实已经让人觉得非常熟悉了。像 DIR、COPY、TYPE、FORMAT 这样的命令,后来都成为 DOS 时代非常常见的命令。 CP/M 由 Gary Kildall(加里·基尔代尔) 在 20 世纪 70 年代开发,最初的全称是 Control Program/Monitor,后来 Digital Research 也使用过 Control Program for Microcomputers 这个解释。它曾经是早期 8 位微型计算机时代最重要、最流行的操作系统之一。 在 IBM PC 出现之前,CP/M 已经拥有相当成熟的软件生态。1981 年 IBM 推出 IBM PC 后,微软提供了 MS-DOS。MS-DOS 的早期设计、命令风格以及整体使用方式,都明显受到了 CP/M 时代的影响。 所以今天再看到 CP/M 的界面,会有一种很有意思的感觉:它看起来很像一个“DOS 之前的 DOS”。如果把个人电脑操作系统的发展画成一棵家谱,那么 CP/M 无疑是其中非常重要的一位祖先,而 DOS、Windows 以及后来整个 PC 软件生态的发展,都可以从那个时代找到不少影子。 [caption id="attachment_72994" align="alignnone" width="816"]CP/M操作系统 CP/M操作系统[/caption] [show_posts keyword="操作系统"] [show_file file="/var/www/wp-post-common/justyy.com/nostalgic.php"] 英文:CP/M - The Operating System That Came Before MS-DOS | Are you this old? Apple CP/M - 1980 Microsoft | Are you this old? Apple CP/M - 1980 Microsoft

又到了一年一度的中秋节/媳妇做的冰皮月饼

2026-09-19 17:54:08

人在海外多年,每到中秋总会多几分“每逢佳节倍思亲”的感触。今年因为朋友做的冰皮月饼,媳妇也被勾起了兴趣,买食材、刷教程、用小美做紫薯馅和咸蛋黄月饼,还把做好的月饼送给朋友。看着她在厨房里忙活,也让我想起了刚来剑桥以及疫情期间那些在家做糕点的日子。月饼或许只是节日的一种形式,但一家人在一起,为生活花点时间折腾,这些平凡的瞬间,本身就是一种团圆。
每到中秋节,总会多几分“每逢佳节倍思亲”的感觉。尤其是在海外生活多年以后,很多中国传统节日反而不像小时候那样,早早就在日历上等着了。 其实每年中秋节快到的时候,我似乎总是最后一个意识到的人。 通常都是某一天去中超买东西,突然看到货架上摆满了各种月饼,才反应过来:哦,中秋节快到了。又或者朋友圈里有人开始晒自己做的月饼,我才意识到又到这个季节了。 至于中秋节具体是哪一天,我往往是不知道的。只知道大概是在秋天,天气开始转凉的时候。 上周,我和媳妇一起去朋友家参加团契。朋友自己做了冰皮月饼,大家一起吃了几个。没想到媳妇回来以后就被“种草”了,觉得自己也应该试试看。 于是接下来的几天,家里就进入了“月饼实验室”模式。 先是在网上各种买食材,然后刷小红书看教程,再把家里的小美 cooker 搬出来研究配方。先折腾紫薯馅,后来又开始做咸蛋黄馅,一做还不是几个,而是一批一批地做。 做多了以后,自己当然也吃不完,于是又装成一盒一盒,送给几个朋友尝尝。 我估计媳妇做月饼的动力大概有两个:一是自己嘴馋,二是纯粹想折腾一下。 我还问她:“为什么不直接买呢?省时省力,想吃什么口味都有。” 她的回答也很直接:“外面卖的哪有自己做的健康。” 这倒也是。自己做,至少糖放多少、油放多少、里面是什么材料,心里都有数。至于到底比外面卖的健康多少,那就另说了。毕竟为了做这一批月饼,这几天厨房也没少折腾。 看着她忙着做月饼,我倒是突然想起了很多年前。 媳妇刚来剑桥的时候还没有工作,时间比较多,经常在家里给我做各种糕点、蛋糕和饼干。那时候隔三差五就能看到她研究一个新配方,然后厨房里摆满各种烤盘、模具和原材料。 后来工作越来越忙,孩子也越来越大,这种慢慢花几个小时研究一种点心的闲情逸致,反而越来越少了。 仔细想想,上一次她这么有时间在家里折腾吃的,好像还是疫情期间。 那时候大家都被要求尽量待在家里,我在家办公,媳妇当时在 Tesco 超市工作,因为疫情期间的一些安排,有一段时间也不用去上班,收入还有保障。突然多出来的大把时间,让大家的生活节奏一下子慢了下来。 那一阵子,家里也经常做各种吃的。 现在回头看,那段时间虽然有很多不方便,但一家人长时间待在一起,也留下了一些很特别的记忆。 所以这次看到媳妇重新开始研究月饼,我突然觉得,做不做月饼本身其实没有那么重要。 重要的是,生活里又出现了这种愿意花几个小时,为家里人折腾一点东西的心情。 人在海外久了,中秋节也许没有国内那么浓的节日气氛,没有满街的月饼广告,也没有几天前就开始讨论放假和团圆。但有时候,一个朋友做的月饼、一盒自己折腾出来的点心,就能把这种熟悉的节日感觉重新带回来。 而所谓“团圆”,大概也不一定非得是一大家人坐在同一张桌子旁。 一家人在一起,厨房里有人忙着做月饼,孩子偶尔过来偷吃一个,做多了再给朋友送几盒——这种普普通通的日子,本身也已经是一种团圆。 又快到中秋了。 以前觉得月饼只是中秋节的一种食品,现在反而越来越觉得,它更像是一种提醒:提醒你秋天到了,也提醒你,在忙忙碌碌的生活里,偶尔还可以停下来,想想家,想想以前的日子,也珍惜一下现在身边的人。 [caption id="attachment_72974" align="alignnone" width="2048"]老婆做出来的第一个月饼。 老婆做出来的第一个月饼。[/caption] [caption id="attachment_72975" align="alignnone" width="1152"]馅饼的馅/紫薯/用小美cooker做。 馅饼的馅/紫薯/用小美cooker做。[/caption] [caption id="attachment_72976" align="alignnone" width="1152"]冰皮月饼装盒 放冰箱冷藏。 冰皮月饼装盒 放冰箱冷藏。[/caption] [caption id="attachment_72977" align="alignnone" width="1152"]大版本的汤圆,然后再用磨具压成月饼形状。 大版本的汤圆,然后再用磨具压成月饼形状。[/caption] [caption id="attachment_72978" align="alignnone" width="1152"]制作月饼的过程,并不简单,也挺耗时的。 制作月饼的过程,并不简单,也挺耗时的。[/caption] [caption id="attachment_72979" align="alignnone" width="1152"]烤出来的月饼是金黄的,很香 烤出来的月饼是金黄的,很香[/caption] [caption id="attachment_72980" align="alignnone" width="1537"]媳妇做月饼 做了好几盒 媳妇做月饼 做了好几盒[/caption] [caption id="attachment_72981" align="alignnone" width="1537"]媳妇批量做月饼,这个是咸蛋黄馅的,要放烤箱里烤。 媳妇批量做月饼,这个是咸蛋黄馅的,要放烤箱里烤。[/caption] [caption id="attachment_72982" align="alignnone" width="1024"]老婆做月饼 老婆做月饼[/caption] [caption id="attachment_73005" align="alignnone" width="1152"]这么一坨紫薯馅 这么一坨紫薯馅[/caption] [show_posts keyword="糕点"]