2025-09-22 08:00:00
标题 Typo 致敬某粗鄙的大佬。
第二次来上海参加 PyCon China,也是我的第七个 PyCon,这次更多是 PyCon for friends。
最激动的是要第一次见到 yihong,当我得知他和我差不多时间到浦东,我就决定等他,哪怕他说下飞机要先拉屎。
结果这小子没同步消息,理解错误,屎没拉也不说一声,把我晾在那,好在等待市域铁发车让我堵到了他。他上来就给了一个拥抱,还把他刚看完的《多情剑客无情剑》送了我,然而是(下),(上)(中)都没有,离谱。
路上商量下午的节目,yihong 先是提议去看电影。我基于以下 3 点提出了反对:
我说难道不是去咖啡馆集体 Coding 吗?我记得和程序员聚会都是这流程,就像过年回老家的固定节目是和同学连坐打 Dota 一样。
yihong 表示,连坐?什么连坐?卧槽咱可以去连坐!
大哥,你是不是搞错什么重点了。
事后才发现,我带的电脑,自始至终都没拿出来过。
和 jay 哥 piglei 接上了头,piglei 游戏上瘾,一听提议打 Dota 坚决支持,看来此事已不可逆转。
先去吃个饭,捡上了高先生、Alex 和马牛。老广高先生给我印象比网上好很多,难怪是妇女之友。
接着四个老年玩家就真去了网吧,等会我记得我们是来开会的是吧?您瞅瞅这是人干的事吗?大哥意识还是在线的,piglei 真是屠夫王,我对 Alex 寄予厚望,毕竟是专业在家打机的,结果最后还是 0-4 收场。
下次得抢白牛。
正打着游戏呢就被催去吃饭,我想起了在网吧还被家长揪的情景。马上马上,就打完这波。
晚上就是 筹划已久 的伊大。阵仗着实不小,我见到了许多网上只知道 ID 的朋友,盐粒带来了 Homebrew,真他妈好喝,我用自己杯子留了一杯给后来到场的老婆喝,大家一面敬仰,一面都主动去喝喜力了,搞得我挺不好意思。
一顿盛况空前,锣鼓喧天,伊大圆满结束,东北大哥差点挂了。
五人住了四个酒店这件事,震惊了我的 J 人老婆。
第二天的 PyCon,yihong 终于露出了他的真面目,原来他是个大佬啊,和我一样是个大佬。
(我建议 Copilot 不要乱补,但 yihong 要求别删。)
上来大哥的演讲真叫牛逼,是我梦想中的演讲效果。
接下来赞助商的演讲就兴趣不大了,溜出去 Social。粉丝围过来合影,要求签名,闪光灯一阵阵的并没有。
整体非常僵硬,碰到了 tygg 跟我撞了衫,相顾无话,愈加社死。还是羡慕卓燃,离开思为后整个放飞自我,我依稀记得去年他还挺正经的。
下午早早就等着听 Gray 的演讲,结果主持人迟到,这是拖堂的开始。
Gray 的演讲非常精彩,不用多说毫无疑问是全场(技术方向)最好的演讲,孩子都听得入迷了。
Gray 演讲完哗啦走了一堆观众。
Manjusaka 演讲前又哗啦进来一堆观众,好像比刚才还多了。Python 3.14 的 tail call 优化,听得我一愣一愣的。
Saka 演讲完教室都空了一半,我想着接下来我的演讲没压力了,那不是随便讲。
事实证明我错了,由于拖堂,大家都把其他会场的听完了来这汇合,我根本没预演过,果然是妥妥超时了,大概超了一倍。
本来还想去代码厨房逛逛,结果人满了。
晚上纠结是去晚宴还是吃面的问题差点闹分裂了,最后还是去了讲师的晚宴。
晚宴在能看到外滩全景的白玉兰广场,Amazing 啊,然而吃的很一般,看着吃面小组发来的照片,我明天高低得去吃一碗。
饭后二场我老婆找了一家精酿,大家相谈甚欢,慧姐忙完已近午夜仍仆仆赶来。这帮程序员虽不胜酒力,未能达到东坡「相与枕藉乎舟中 不知东方之既白」的程度,却也直到凌晨才阑珊而归。
明天将各奔东西,做牛马、带小孩,也不忘今晚在秋风中共饮。春节每年都越过越没劲,但我们找到了自己的春节。最后借用在和菜头文章里看到的一句话,愿大家都能:
肥而不腻,老而不登。
2025-05-11 08:00:00
这不是一篇正经的游记,事无巨细地记录行程、餐饮、住宿并非我所擅长,很容易写成流水账。我不希望文章变成那样,所以只是写下我想与大家分享的东西,记录下河南在我心中的印象。
五一假期我和老婆去了河南,尽管预料到人会很多,但在国家实行统一长假的框架内,还是只能如此。就算我是数字游民,但老婆不是啊,所幸女儿没有和我们一同前往,事后回想这次如果带上了她,想必会艰难加倍。
我看过一本书叫《中国八大古都》,可能大家只对七大古都熟悉,而这本书里提到的第八大古都,就是河南郑州,因为这里发掘了商早期的都城遗址,有郑州商城遗址。这里的道路很宽很平,太阳很大但并不觉热。我们去了两处适合网红拍照的景点瑞光路和油化厂创意园,差强人意。
晚上去吃了葛记焖饼 ,两个南方人只能吃一份,被服务员反复确认,我感觉受到了鄙视。
今天安排了重头戏,就是去郑州的「只有河南」戏剧幻城。这是一个有着 21 个剧场的戏剧主题园区,其中有 3 个主剧场,买的单日票是包含一个固定场次的其中一个主剧场,也不用贪心,因为要看完全部 3 个主剧场, 没有两天是看不下来的。事先我们做了周密的攻略,做了 A、B 计划防止时间赶不上。最后在 B 计划的大框架内做了一些小调整完成了一天的任务,看下来是 1 个主剧场 7 个小剧场。感觉已经到了极限,因为其中《苏轼的河南》和《红脸蛋儿》都是网红热门,都提前排队了。园子是 10:00 开放,我们上午 9:50 就进园,晚上 8:30 出园,已经拉满了。第二天是五一假,园区开放时间会大大延长,但我们惧怕人流,选择避开了。
剧场名 | 时间 |
---|---|
麦子啊麦子 | 10:20-10:48 |
火车站(主) | 11:00-12:05 |
天子驾六 | 12:30-12:52 |
候车大厅 | 13:30-13:55 |
苏轼的河南 | 15:30-16:00 |
红脸蛋儿 | 17:00-17:27 |
曹操的麦田 | 18:00-18:35 |
张家大院 | 19:20-19:50 |
这一整天的饭就别指望好好吃了,中午干粮应付,晚上坐园区的公交回市区(园区其实已经在中牟县了),回宾馆的时候已经 10 点多了,随便吃了点烧烤外卖。
剧场整体是以 1942 年的河南饥荒做线索,串起三大主剧场和各小剧场,其实每个剧场,单独拎出来,都属于卖不出票的情况,但合在一起他们就成立,且游客趋之若鹜,这就是「只有河南」在商业上成功的地方。感觉挺好的,既让大众有机会戏剧启蒙,又能养活一些十八线的演员们。园区入口和二层都种了大量的麦子,临近立夏,都长得很好,大片的青绿色,据说到了芒种麦子会变黄,那时去可能会看到不一样的景象。
这天没别的,就是河南博物院了。我喜欢逛博物馆,但又不带讲解,能接收到多少纯看缘分了,也不是走马观花,每个展馆都会认真去了解。今天人流自然是惊人,所以必须反着参观,进来就直上 4 楼然后看下来。 这里的几大镇馆之宝还是有点含金量的,楚文化展馆的云纹铜禁的镂空装饰非常精妙,武则天的金简也是拍照的人山人海。
顺带一提,藏在河南院的「妇好鸮尊」将自 5 月 19 日起在北京大运河博物馆展出,它本是一对,这次它将与另一只藏于国家博物馆的鸮尊合体展出,在北京对青铜器感兴趣的值得一去。
我们一直在博物馆参观到下午 2 点多才出去,步行去「合记烩面」吃了一碗烩面,那是真好吃。然后去「阿财咖啡」每人点了一杯,拿到咖啡时的我眼泪差点掉下来,13 块钱的美式足足有一升!
一大早就起床赶六点多的城际去开封,你问我为啥不提前一天去开封入住,那当然是因为贵啊。没想到开封已是河南第二大旅游目的地,仅次于洛阳,而宾馆酒店明显承载不了这么大的游客数量,只能一再涨价,一间小小的如家都要 800 多,这价钱能在郑州住大 house,它不香吗?
今天计划是清明上河园,别提了,这是比只有河南还要艰巨的一天。这是一个照搬《清明上河图》的全仿古建筑的主题乐园,内容依然是大大小小的表演,比如包公巡视汴河,岳飞枪挑小梁王之类的。看只有河南时还不觉得,现在才终于知道每年一大批表演专业的演员们毕业们都去哪了。他们每人从早演到晚,重复着一次又一次相同的走位相同的动作,还要上马战,只能感慨没有一个牛马是轻松的。对了,闭园时间是凌晨 1:00 哦。
除了人还是人,每场表演都里三十层外三十层,比肩接踵,重点演出你还必须提前占座,对我们这种单日的外地游客太不友好了。带小朋友的更是遭罪了,全程只能看屁股,所以这是为什么我庆幸没带女儿来,起早贪黑特种兵不说,还没有体验。
一天演出的重头戏就是晚上的打铁花表演和《岳飞郾城大捷》,我们毅然选择了提前两小时去占岳飞的座,然后看晚场的打铁花。事后证明这个选择无比正确,因为到晚上七点钟突然狂风大作电闪雷鸣,进而下起了大雨,而我们在的观看岳飞的场地是有顶棚的。我们等了两个小时,不仅看到了有上天赠送雷电特效的《郾城大捷》,还在之后的打铁花中坐到了第一排。
雷电加持的《岳飞郾城大捷》(来自小红书):http://xhslink.com/a/UhoRDNI06Fvcb
不得不提的是这里的物价,这么多演出随便看,一人只要 120,就连园内的可乐也只要 5 元一瓶,我从来没见过 5 元的可乐,如果你买了《东京梦华》的演出票,还能三天内随意进园。这价格放眼全国也是相当良心,难怪人都成山了。其实开封还有另一个类似的乐园叫万岁山武侠城,但据说人数比清园还多得多我们就放弃了。
今天不得不下榻开封,住的地方其实是一个洗浴中心,也算打开思路了。
今天参观了开封博物馆,吃到了宋园的灌汤包和化三驴肉火烧,下午逛了开封府。
总体来说,如果你是古建爱好者,喜欢寻找历史的遗迹,那开封作为七大古都之一是乏善可陈,古建全是现代仿的,博物馆展品也没有含金量,唯一一个开封府题名记碑还到处复制,我不知道看到多少块,其实只有博物馆里那块是真的。但是清明上河园和万岁山武侠城这两个沉浸式复原古代生活场景的园区,绝对是能值回票价(那场打铁花和烟火表演,在我看过的所有里面都算顶级的,单这一场表演就能值 120 块),推荐一去。
晚上实在不能再住洗浴中心了,坐高铁颠去安阳了。
古都之旅的最后一站是安阳,我们当然是慕殷墟之名而来。如果中国每个朝代能挑出一个博物馆来代表它,那么商代绝对是殷墟博物馆,19 世纪末因为一次抓药发现了甲骨文这个宝库,将中国信史上推一千年。 在文字发展的初期,文字还没有形成系统,很多字都是临时组合而成,比如商朝每代君主都有一个专属的合成字。而且人们还会充分利用象形文字的优势,随意改笔,表达特定的意思,比如下面这块龟甲:
圈出的两字其实都是「车(車)」,但仔细观察,上面的车轴断了,下面的车上下翻转了,这段卜辞其实讲的是王出猎,追犀牛,结果发生了轴断,车翻,人坠的事故。
另外还参观了很多的青铜器,现在我已经是青铜器小能手了,能准确辨别属于哪种青铜器型。
至于殷墟宫殿遗址就没什么好去了,房子不可能有真迹,妇好墓能下去,但什么也没有。大家以后要是来殷墟,只用 80 块的博物馆门票就行了,要是下午 5:30 以后进,是不要钱的。
不管之前网上大家对河南人是怎么调侃的,河南给我的印象就是一个厚道的北方人,你来他家玩,他恨不得把他家最好的东西拿来给你看给你吃。凉菜给你塞满,表演给你看足。 我上次来河南还是 2010 年的洛阳,那时洛阳远没有现在这样热门,当然那时我也穷得很,连龙门石窟都没去,我想可能还会再来吧。
这几天强度还是非常高的,有时候正餐都没法好好吃,谁让我五一出门来着了。
2025-03-05 08:00:00
起笔的原因是群里的一段聊天:
不禁感叹 Singleton(单例模式)作为一个经典的设计模式,是如何被滥用的,特别是在 Python 这门语言中。它竟然成了一个八股式的面试题,就像「茴字有几种写法」一样,一直被问个没完。但我敢说,绝大多数人回答的时候,都是照本宣科,他们参考的网上的答案,也很少有能讲正确的。
先说结论
至于原因,让我们来看看几种流行的 Singleton 实现方式:
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
# Usage
@singleton
class MyClass:
...
如无特殊说明,代码均由 Copilot 提供
这个方案的问题很明显:应用装饰器后改变了对象的类型,由一个 class 变成了一个 function。假使有人要用 isinstance(obj, MyClass)
来判断对象类型,就会报错。
这又涉及另一个问题,你的代码将被如何使用,取决于你暴露了什么,上面的例子中暴露的就是 MyClass
这个对象,那就要考虑会不会被当成 class 来用,以及若被这样使用,是不是合理的要求。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
这个方案的问题是,如果有多个单例类型,就得写多个这样的类,而且这个类也不能被继承,继承之后,实际上还是共享同一个 _instance
变量,产生冲突,这不是我们想要的。还是老问题,你暴露了一个类,别人就会用类的方式来用。
第二个问题,这个方案没有屏蔽 __init__
的调用,实际上你如果多次实例化 Singleton 类,虽然返回的对象 id 唯一,__init__
方法还是会被调用多次,这可能不是你想要的。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
print('init called')
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
# Output
# init called
# init called
# True
class Singleton:
_instances = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instances[cls]
# Usage
class MyClass(Singleton):
...
class MyAnotherClass(Singleton):
...
这个方案暴露的对象其实就是一个基类,它的标准用法就是让你用来继承的,它解决了上个方案的第一个问题,但第二个问题依然存在。
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# Usage
class MyClass(metaclass=Singleton):
...
这个方案用了元类,就是所有致力于掌握 Python 高级语言特性的人们会想到的终级方案。它在元类级别直接拦截了实例化的调用,而不仅仅是 __new__
方法,因为实例化的调用包括了 __new__
和 __init__
,这样就解决了上面提到的第二个问题。如果你再运行上面的测试代码,会发现输出符合期待了:
s1 = MyClass()
s2 = MyClass()
print(s1 is s2)
# Output
# init called
# True
很完美,不是吗?先别沾沾自喜,看下面的代码:
class MyClass(metaclass=Singleton):
def __init__(self, name: str) -> None:
self.name = name
s1 = MyClass('Alice')
s2 = MyClass('Bob')
print(s1.name, s2.name)
# Output
# Alice Alice
你认为这个输出符合编写者的意图吗?不能说是也不能说不是,只是值得商榷,这取决于调用者如何看待单例模式。一个考虑是如果用方案 2 和 3, name
属性的值就会被统一改成 Bob
,这本身已经体现了问题所在。你可能会说没人在单例类的实例化中传参,这点我也不确定,但我认为,有人这么做,是因为你暴露的接口允许他这么做。
class Singleton:
...
singleton = Singleton()
# Usage
from singleton_module import singleton
朴实无华,没有花里胡哨的东西,用这个答案如何能体现我精通 Python 呢?相反,我认为这个方案是最能实现原始需求的,相对来说问题最少,也最容易理解。就像人生的三重境界一样,最终还是要对花哨的东西袪魅,回到需求本身上去。这个方案里暴露的对象是唯一的 singleton
模块变量,你不可能用类的方式来用它,也不可能传参给它,这才是我们想要的。你甚至可以把类名写成 _Singleton
断了人的念想(当然这不是硬禁止,你想用还能用,别在这上面抬杠了)。
那如果,我还是想暴露 Singleton
类来做一些比如 isinstance
的操作呢?我承认有这个需求,那我们稍加改造一下:
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is not None:
raise TypeError("Singleton class cannot be instantiated twice")
cls._instance = super().__new__(cls)
return cls._instance
singleton = Singleton()
看上去好像跟方案 2 差不多,但暴露的主要对象已经从类变成了实例,这个类已经不允许做实例化的操作了。这就是从代码层面控制了暴露的接口。
2024-12-24 08:00:00
在 BentoML lead 了几个重要的 feature,包括 1.3 和 1.4 两个大版本,以及 Codespaces 和 Comfy-Pack。
11 月参与 PyCon China 2024,见了许多网友,参加了代码厨房的活动,很开心。做了关于 Pydantic 的演讲。
11 月参与了 NebulaGraph 深圳的线下活动,做了关于 BentoML 的分享。
5 月份有荣幸与 Paul Romer 见面交流,并造就了 2024 年我最受关注的一篇文章。但于我来说会总结为机缘巧合,不必赋与我其他的光环。事实上从那以后我试图联系过 Romer 先生都没有得到回应。
开源了 FxZhihu 被很多人使用,感受到贡献了一些价值。
写作博客文章 15 篇。
由于 uv 的横空出世,PDM 无疑受到了一些冲击,发展明显变缓。
但我不是那么狭隘,人不能沉湎在过去的辉煌中举步不前。好用的工具得用,我自己平时也会用用 uv。PDM 也在 2.19 中加入了对 uv 的支持。
开源了 Tetos,一个 TTS 的统一 SDK 库。
其他的一些娱乐项目比如:
2024 依然目睹了 GenAI 和 LLM 的火爆,我个人也体验了 Cursor 最后又回到了 VSCode。
今年继续去了挺多地方。
1 月份汕尾
2 月珠海——中山
春节马六甲——吉隆坡
清明节广西梧州
4 月份上海
五一普者黑——弥勒
6 月份泉州
8 月份太原
国庆日本九州
11 月潮汕
PyCon China 上海 Again
12 月香港
最喜欢的书是《隳三都》,在历史非虚构里属于非常优秀的了。另外还有刘震云的《我叫刘跃进》,高级的黑色幽默。 电影没有最喜欢的,相对好的是《走走停停》《从 21 世纪安全撤离》和《死侍与金刚狼》。
只是 10 月份以后就没有看书和电影了,提不起兴趣来。
明年会怎么样,我也不知道。2025 是个平方数,希望是个好年。
$$ 2025 = 45^2 = 3^4 \times 5^2 $$
本文题图由 Ideogram 生成,其余图片,除 PyCon China 合影,均为本人拍摄。
2024-12-18 08:00:00
最近社区里有人提了一个PR^1,解决了一个潜在的内存泄漏问题。我认为这个问题在很多场景都容易忽略,所以分享在这里。
上代码
import gc
class Foo:
def bar(self):
print("bar")
foo = Foo()
print("Before", gc.get_count())
foo.bar = foo.bar
print("setattr", gc.get_count())
del foo
print("After", gc.get_count())
运行发现 foo
对象无法被正常回收,造成了内存泄漏。这是因为看似不起眼的一行 foo.bar = foo.bar
,实际上创建了一个循环引用。
点解?
因为,foo.bar
这个表达式,实际上执行了 Foo.bar.__get__(foo, Foo)
,返回了一个绑定了 foo
的方法,此方法中持有 foo
的引用。而 foo.bar = foo.bar
,相当于把这个结果缓存在了 foo.bar
属性上。之后的 foo.bar
只是从属性上取值,这样 foo
和该方法互相持有对方的引用,出现了循环引用。
现实中我们不太可能会写出 foo.bar = foo.bar
这样的代码,但是在 monkeypatch 场景下,可能很容易被坑到,比如:
import requests
resp = requests.get("https://example.com", stream=True)
resp._fp = CallbackFileWrapper(resp, callback)
...
上述代码 patch 了 requests
库的 Response
对象,让它在被读取完成中调用我们指定的 callback
函数。这里 CallbackFileWrapper
会持有 resp
的引用。
如何避免?
在上述例子中,我们可以使用 weakref 来避免循环引用:
import weakref
class CallbackFileWrapper:
def __init__(self, resp, callback):
self.resp_ref = weakref.ref(resp)
self.callback = callback
def read(self, size):
resp = self.resp_ref()
if resp is None:
return b""
read_bytes = resp.read(size)
if not read_bytes:
self.callback(resp)
2024-11-07 08:00:00
我女儿第一天上幼儿园回来,我就拿着群里他们班级的合照,挨个问她每个小朋友都叫什么名字。一方面是想看看她集体融入得怎么样,另一方面是自己想留个底,争取每个小朋友的脸和名字都能对上。 她当然不能每个都叫出名字来,但配合群里其他家长聊天的信息,三天后我就基本能全叫出名字了。有回我正在问,丈母娘见了在旁说,「知道这个有什么用,你又不是老师。」面对这种言论,我只能一笑了之。
我觉得,总会有用的。在我看来,至少有下面几个用处:
但丈母娘这个看法就见识短浅了,她才是接送孩子的主力。我已经不知多少次听她说路上碰见个幼儿园同学,做了什么举动,却不知道是谁。
今天去幼儿园参加家长开放日的活动,其实就是小朋友在上课活动时家长在旁观看。户外休息时,很多小朋友围到我身边来,因为他们发现我能叫对他们每一个人的名字,甚至还知道小名。于是就疯狂地来考我,乐意跟我聊天。 我突然觉得,两年前做过的功课没有白做,今天派上了用场,不是什么大用,能温暖我一阵子,也足够了。
所以,你看过的每一本书,玩过的游戏,走过的路,都不会浪费。只是你还没找到用处罢了,又或者它们已经内化在你的个人气质中了。
功不唐捐。