2026-01-24 09:18:00
在学习 Race 或原子操作时,往往会有一个很经典的示例
1 |
#include <stdio.h> |
用 ./race 2000 5 来运行它,最终会创建 5 个线程并发对一个全局变量自增,因为这个「自增」操作不是原子的,所以最终的更新结果往往是一个小于 10000 的数字
但是,如果把这个代码翻译成 Python
1 |
import sys |
你会发现无论执行 python race.py 2000 5 多少次,最终的结果永远是稳定的 10000 —— 竞争消失了
没错,稍有经验的人几乎可以一下子反应过来这是 GIL 的锅!
Python 3.10 起引入了一个优化,并不是所有操作都会去进行获取 + 释放 GIL 的操作。而我们想要产生竞态的核心逻辑 [LOAD_GLOBAL counter, LOAD_CONST 1, INPLACE_ADD, STORE_GLOBAL counter] 正好都不在这些 GIL 操作上,因此上述代码看似是两个线程并行,但实际上是分开执行的,所以并没有产生竞争、没有同时读写一个全局变量的情况出现,导致无法复现
既然它是 GIL 导致的,那我们关了 GIL 不就好了🤔 Python 3.13 起开始有了一个 free-threaded 版本(常称为 python3.13t ),将 GIL 剔除了,所以我们如果直接用这种不含 GIL 的发行版去执行上面的程序,可以发现竞态成功复现了
既然问题出在我们的代码「过于简单」以至于不会给 GIL 释放锁的机会,那我们就手动给它引入释放的机会就好了,最简单方法就是利用「函数返回」—— 我们只需要将 += 1 替换成一个函数调用即可
1 |
import sys |
counter += one() 的指令是 [LOAD_GLOBAL counter, CALL_FUNCTION x, INPLACE_ADD, STORE_GLOBAL counter] ,而其中的 CALL_FUNCTION 会触发 GIL 切换检查,即在读写的过程中给了 GIL 释放锁的机会、允许了竞争
不过注意一点,因为现在 GIL 是 time-based switching,默认要 5ms 才会触发一次线程切换,所以每轮多跑几次,例如用
pythonrace.py200000 5- 少了的话在现代 CPU 上可能根本来不及满足切换的条件程序就运行完了
2025-10-16 13:52:00
NULL 有一个很重要的特性:NULL 与任何值的运算结果都是 NULL
1 |
SELECT 1 = 1; -- true |
这也就意味着,对于存在列 col BOOLEAN NULLABLE 的表,如果一行中 col 列的值为 NULL,则下面的四条查询都不会包含这行
1 |
-- 等于自不必说 |
我们其实基本都知道这点,所以我们在进行不等运算时通常会用 IS NULL / IS NOT NULL 特殊处理 NULL 列
1 |
SELECT * FROM "table" WHERE col is null OR col != true; -- 筛选 col = null 或 false |
但其实,除了 IS NULL, IS 操作符后面还可以跟随 BOOL 值,那么,用 IS NOT 其实是一个更加精准的不等于操作
1 |
SELECT * FROM "table" WHERE col IS NOT true; -- 筛选 col = null 或 false |
而对于不是 bool 类型的 nullable 列,我们则可以搭配使用 = != 与 IS
1 |
-- 筛选 col = null 或任何不为 1 的值 |
NULL 与任何值的运算结果都是 NULL,但在函数中则不一定(取决于具体实现)
以连接字符串为例,如果中间存在 NULL,用 || 连接属于运算符,中间任何一项为 NULL 则结果为 NULL,而用 concat 连接则是函数,中间出现的 NULL 会被忽略
1 |
SELECT 'hello-' || NULL || 'world'; -- null |
NULL 与任何值的运算结果都是 NULL,但在布尔逻辑中则不一样…… 试试看能不能说出下面的返回值
1 |
select not NULL; |
1 |
select not NULL; -- NULL |
那么自运算,无论「未知的反面」还是「未知和未知」「未知或未知」都是未知,也因此都是 NULL
OR 的逻辑则是「有任何一个值为 TRUE 就是 TRUE」,因此,在 NULL 参与的运算中,存在 TRUE 则为 TRUE,不存在 TRUE 则为 NULL(继续未知)
AND 的逻辑则是「有任何一个值为 FALSE 就是 FALSE」,因此,在 NULL 参与的运算中,存在 FALSE 则为 FALSE,不存在 FALSE 则为 NULL(继续未知)
在聚合时,如果对 NULL 值进行聚合,它的数值是被完全忽略的 —— 一个例子是 avg,如果对 1, 2, 3, 4, NULL 做 avg,结果是 (1+2+3+4)/4
如果期望给 NULL 在聚合时一个默认值,可以用 coalesce 函数为它赋一个「默认值」
上面已经提过 coalesce 函数了,再说一句就是,coalesce 函数可以接受多个参数(而不只是两个),会返回第一个非 NULL 值
例如用 coalesce(first_name, nickname, email) 取用户「昵称」
pg 中还存在一个 nullif 函数,接收两个值,如果两个值相等则返回 NULL 否则返回第一个值,等价于 CASE WHEN a = b THEN NULL ELSE a END
这个函数主要用来「将零值转换为空值」;举个例子就是
1 |
SELECT |
还有就是搭配 NULL 的「可转换为其他类型」的特性
1 |
SELECT |
虽然大概率不会引起混淆,但还是说一下,虽然我们用 psql 规则看到 NULL 值就像是文本一样,但它的底层传输是二进制,PG 规定用 len = -1 代表 NULL 值,所以不存在它与空字符串、null 字符串等混淆的情况
另外,特殊的,在 COPY 中,如果用的 text format(PG 的消息协议中,对于一个值存在 text 和 binary 两种 format),那么会用 \N 代表 NULL 值
参考:https://www.postgresql.org/docs/current/protocol-message-formats.html 中的 Query, Parse, Bind, RowDescription, DataRow
当行中有任一列值为 NULL 时,「行头」HeapTupleHeaderData 中的 t_infomask 字段内的 HEAP_HASNULL flag 会被标记为 1,此时在行头后面、其余数据前面会增加一个 null bitmap,用位图的形式存储所有列的 NULL 情况,且如果某一列的值为 null,后面的 data 中将不会出现这一列的信息
另外,null bitmap 除了用来处理 NULL 值,还会用来处理 drop column —— 当一列被删除时其实际上依然存在着,只是后面所有的行都会带有 null bitmap 将这一列标记为 NULL(更事实上,删除列是懒删除,行内的数据都还在,只有下次更新行时才会清理这些数据 —— 当然,清理的方式也是将它标记为 NULL);
参考:https://www.postgresql.org/docs/current/storage-page-layout.html
2025-10-06 11:12:00
我之前的 Zeabur 集群是独立跑在一台物理机上的,物理机相比云服务器的劣势之一就是底层存储的数据安全性 —— 云服务器的硬盘通常是由云服务商保证了安全性不同,物理硬盘坏了就是坏了
而不幸的是,不久前我就遇到了…… 所有数据差点消失,幸亏坏的盘并没有完全坏,以只读方式还能读(只是不能写)算是挽留了我的数据
但那以后我就一直在思考怎么保证数据安全,经过数个不同的方案研究,我最终的选择是 —— 将整机移动到 PVE 中 然后在 PVE 的底层使用 RAID 1 硬盘
方案敲定,行动开始 —— 我整个的迁移不太具有可复制性,因此写一篇完整的「迁移指南」确实不太有意义,但中间确确实实遇到了一些小问题,我觉得记录下来还是比较有价值的
💡 我两台机器(原 Zeabur 物理机和新的在 PVE 里面装的虚拟机):
- 在同一个机房
- 都是用的 RHEL 系的系统
- 内部文件系统都是 LVM + xfs
我的 PVE 并不是在安装时就直接做好 RAID1 的,因此保证安全性的第一点是将 PVE 的硬盘改成 RAID1
⚠️ 需要注意的是,RAID 只是冗余,不是备份也无法替代备份
这次我的迁移也顺便把备份加上了,但备份又是一个较大的话题,因此本文不会写备份相关的内容,如果未来有机会我会单独写一篇文章,不过可以顺便说一嘴的是,我选用的备份方案是 Velero
我没有阵列卡,那么硬盘改 RAID 就只能是通过软 RAID 来组,在 Linux 下组软 RAID 有两种办法,一是利用 mdadm 组 RAID,另一种则是用 lvm 组 RAID;二者各有优劣,我为了灵活性选择了 lvm 的方案 —— 我现在是 2 块 1TB 的硬盘,使用 lvm 的话后面扩容可以很方便的通过加 1 块 2TB 的硬盘来得到完整的 2TB 可用 RAID1 空间,而如果不用 lvm 虽然也能实现但相对来说更加麻烦,且有难以避免的 degrade 时段
哦对,还有,做冗余不能只将数据做冗余,也要考虑引导,都是老步骤,复制下 ELF 分区、重做下 grub 引导,没什么值得说的,有什么问题问问 ChatGPT 它应该能相当完美的回答。
假设我们现在的环境是:原有的数据卷 /dev/sda3、原有的 VG pve、原有的 LV pve/root /pve/swap pve/data、新的数据卷 /dev/sdb3
首先将新的数据卷转换为 PV pvcreate /dev/sdb3 然后将它加入至 VG vgextend pve /dev/sdb3
将 pve/root 转换为 RAID1 十分简单:一行命令 lvconvert --type raid1 -m1 pve/root /dev/sda3 /dev/sdb3 搞定
pve/swap 是个 swap 分区,没有做 RAID1 的必要,我们跳过
难点来到了 pve/data 这个逻辑卷 —— 它不是一个普通的 LV 而是一个 Thin Pool,如果我们直接执行 lvconvert --type raid1 -m1 pve/data /dev/sda3 /dev/sdb3 LVM 会报错 Operation not permitted on LV pve/data type thinpool.
可以认为 Thin Pool 是一个「逻辑逻辑卷」,它事实上分成了 meta 和 data 两部分(通过 lvs -a 命令可以看到,它是用 pve/data_tmeta 和 pve/data_tdata 组合而成的),所以我们说是对它做 RAID1 但事实上想做的是对它底层依赖的 meta 和 data 做 RAID1 —— 这样,Thin Pool 本身和从这个 Thin Pool 所分配出去的子 LV 也都是 RAID1 了。
所以我们要做的是,将它的底层数据卷转换为 RAID1 即可:
1 |
lvconvert --type raid1 -m1 pve/data_tmeta /dev/sda3 /dev/sdb3 |
然后等待 lvs -a 的输出中 Cpy%Sync 列变为 100% 即为 RAID 转换完成。
哦对,还有一件事情,虽然示例中我用的另一块盘也是 SATA 盘做例子,但事实上我的另一块盘是 NVME 的,因此我上面说的「pve/swap 不用做 RAID1」之外,我其实还将 pve/swap 给移动到了新的盘上
将 LV 从一个 PV 移动到另一个 PV 的命令:pvmove -n pve/swap /dev/sda3 /dev/nvme0n1p3
物理机迁移到 PVE 最直接的方法就是直接做整盘磁盘镜像然后导入,这种方案没什么可说的,也是最简单的
但是基于下面几个原因
我最后放弃了整盘迁移的方案,而是选择 rsync 拷贝 k3s 相关的数据:
/usr/local/bin/k3s-killall.sh 停止整个集群、删除给 zeabur 用的 ssh key、在 zeabur 中删除这个服务器我之前使用的防火墙时 ufw,它简单易用,但有一个非常重要的缺陷:只要 k8s 暴露的端口,它没办法阻止
因此,趁着这次,换回 firewalld 了;根据 Zeabur 的说明和 k3s 文档,需要执行下面的内容放行相关流量
1 |
firewall-cmd --permanent --zone=public --add-service=ssh # 22 |
注:我个人不建议放开 30000-32767 —— 我询问过 Zeabur 支持人员,不放开这些端口不会影响 Zeabur 本身的能力,只是非 http 类型的端口映射无法被访问。Zeabur 会默认将所有端口都暴露到公网,部分端口所对应的服务可能有安全问题,所以我的建议是不要添加 30000-32767 两台规则,而仅在确实需要访问映射的端口时再添加
注 2:如果你是基于你自己需要的目的去访问内部服务(如数据库、Redis),我非常不建议你将这些服务暴露到公网,而是应当使用 zproxy 通过代理访问这些内部服务(当然,你需要将 zproxy 本身使用的端口放开)
zeabur 和 k3s 本身对于「本机 IP 」有一定的偏好,如果要改的话比较麻烦,所以简单起见,我的选择是:
假设原机器 IP 1.1.1.1,现在新的机器 IP 2.2.2.2,我将两个机器的 IP 对调下就好了
云主机换个 IP 轻轻松松,物理机换个 IP 就很麻烦,特别是远程物理机换 IP(好吧,其实可以直接登录 IPMI 在 Remote console 里面改 IP 的,但我们当这个不存在)
不过挺好的一点是,我可以通过引入一个新的临时 IP 让这两台机器都不离线的情况下对调下 IP
💡 我这两台机器在同一个内网、网关相同、DNS 相同,都是手动指定的 IP 没有使用 DHCP
假设两台机器的网卡都是 eno1、临时 IP 是 3.3.3.3
流程是:
/usr/local/bin/k3s-killall.sh 把整个机器的 k3s 停掉nmcli con mod eno1 +ipv4.addresses "3.3.3.3/24" && nmcli dev reapply eno1 让这个机器有 1.1.1.1 和 3.3.3.3 两个 IPnmcli con mod eno1 -ipv4.addresses "1.1.1.1/24" && nmcli dev reapply eno1 让这台机器只剩下 3.3.3.3 一个 IPnmcli con mod eno1 +ipv4.addresses "1.1.1.1/24" && nmcli dev reapply eno1 让这个机器有 1.1.1.1 和 2.2.2.2 两个 IPnmcli con mod eno1 -ipv4.addresses "2.2.2.2/24" && nmcli dev reapply eno1 让这台机器只剩下 1.1.1.1 一个 IPnmcli con mod eno1 +ipv4.addresses "2.2.2.2/24" && nmcli dev reapply eno1 让这个机器有 2.2.2.2 和 3.3.3.3 两个 IPnmcli con mod eno1 -ipv4.addresses "3.3.3.3/24" && nmcli dev reapply eno1 让这台机器只剩下 2.2.2.2 一个 IP🤷 显得繁琐了点,但其实不难
主要有两部分的信息我们需要迁移
我们需要用 rsync 同步下面的文件/目录到新的集群:
/etc/rancher/k3s k3s 配置及连接集群的凭证/var/lib/rancher/k3s/server/db/ k3s 数据库/var/lib/rancher/k3s/server/token k3s 内部授权 token/var/lib/rancher/k3s/storage 使用的 local volume 的存储路径同步时使用的命令为(在原机器上执行)
1 |
P=/etc/rancher/k3s # 依次使用上面所需要同步的路径 |
在启动前,删掉下面的目录(k3s 启动时会自动重新创建)
/var/lib/rancher/k3s/server/cred/var/lib/rancher/k3s/server/tls我为虚拟机分配了 512GB 的硬盘,但我没想到的是我使用的 Automatic Disk Partition 竟然将绝大多数空间给了 /home 导致我迁移数据一半告诉我没空间了 = =
幸好,虽然这个奇怪的硬盘分区有点烦人,但 RHEL 系一律使用 LVM —— 它也只是个 LV 而已!简简单单,删掉这个 LV 把空间匀给 root 就好了
对了,删除 /home 挂载点要记得改 /etc/fstab,不然下次系统可能启动不起来
因为 Zeabur 不允许同一个 IP 有两个 Dedicated Server 出现,所以安装的过程中是使用了一个新的 IP 装的
在迁移完,如果想用回原来的 IP,需要修改 /etc/systemd/system/k3s.service.env 文件里面的 K3S_NODE_NAME
然后启动起来看看 kubectl get nodes,如果还有原来的 IP 的 Node,删了就好
我数据迁移完一启动各种飙红,仔细一看原来是 Pod 数量超限(默认 110),需要修改 kubelet 配置
在 /var/lib/rancher/k3s/agent/etc/kubelet.conf.d 目录下,创建一个 01-max-pods.conf ,里面写
1 |
apiVersion: kubelet.config.k8s.io/v1beta1 |
然后执行 systemctl restart k3s 就好
哦对,这个其实我之前就改过,但是迁移的时候漏了,你可以看看原来机器的 /var/lib/rancher/k3s/agent/etc/kubelet.conf.d 目录下有没有除了 00-k3s-defaults.conf 以外的文件,有的话最好前面 rsync 的时候直接一起迁移了,省事
2025-09-19 08:27:00
ChatGPT 目前已经成了我的长期订阅选择
一方面,GPT-5(和原来的 o3)体验确实好,可以在思考的过程去搜索、执行代码简直是王炸
另一方面,Codex 写代码是真的强,绝大多数能力已经能超过 Claude 了,只有 Codex 软件本身的用户体验差了一点
还有一点,Codex 自带 PR 代码审查!而且是完全免费的,完全可以替代掉 Code Rabbit / Cursor 之类的产品了
这一切只要 $20 真的很值
Proton Visionary 依然在我的订阅列表中
但我已经越来越少用它了,目前没有退订一方面这东西退订需要 Deactivate 邮件地址。。另一方面则是我的上古版 Visionary 价格太香了(现在退了再买差不多涨价了得有 70%)
目前我已经停用了绝大多数 Proton 的组件,只在使用 SimpleLogin 和 Mail。
btw,如果你也在用 Proton Mail 但是不想用它的官方客户端(官方客户端实在太难用了,改版前难用,改版后不但难用 bug 还多)可以考虑各种第三方邮件客户端 + protonmail-bridge-docker 方案
AD:Proton Visionary 可以分享给他人加入,如果你想加入我的家庭组请发邮件给我 [email protected],价格为 92 天 ¥109
没错,我依然订阅着 Cursor 🤔 Cursor 在折腾它的 plan 把自己折腾的残废的情况下,我依然在订阅着它😂
首先一点,Cursor Tab 是真的强,市面上没有对手。我最初就是完全因为 Tab 而订阅的 Cursor,而现在也经常用它来修一些 AI 搞不定的复杂逻辑。
另外,看似 Cursor 涨价了,但作为老用户还能继续享受一个月 500 次(Opt out of new pricing)。而现在的 500 次是不再限制 20 个工具调用的 500 次,比 max 只差在了 context 长度上(但我基本都会拆分好任务再给 AI,达到 200k 的次数屈指可数 —— 而且就算达到了 Cursor 也会自动 compact),所以整体依然很香(至少在强制 usage-based billing 之前很香😂)
在 AI 时代,一个独享的家宽 IP 可能已经是想流畅使用各种服务的必备品了;之前的 IP Royal 虽然便宜但是质量真的不敢说好,目前换成了 lisa,相当棒
AD:要购买欢迎使用我的推广链接 https://lisahost.com/aff.php?aff=3372
Dler 的服务我觉得已经回到了原来的水平,所以我目前也回到了 Diamond Plan,我觉得目前它又一次成为了我心目中的第一(至少比某 N 家强 emm)
曾经我是 Premium 订阅,在今年中升级到了 Lifetime
对于想学习 k8s、网络、Linux 的,很推荐这个平台,Learn by doing 的形式,边学习边实践还有对应的测试,绝对比单纯看某些文档/博客更适合学习
而且,在过去一年的更新中,iximiuz labs 增加了不少新功能,对我最重要的就是自定义 Playground —— 想学习/体验下什么产品,可以快速创建一个去测试,而且多机器的设计也可以自由去测试集群相关功能。
还有一些仍然在订阅的,但与《2024Q3 订阅 Recap》相比没什么变化或没什么想写的,就不再赘述了,在这里列个清单,感兴趣的可以回看我之前的 Recap
首当其冲的自然是 Claude 了;其实到现在 Claude 都是桌面 MCP 做的最好的、Claude Code 也是综合体验最好的,奈何 Opus 持续降智的同时 Codex GPT-5 太强了而且性价比真的高……
我曾经对于 Monica 十分满意(可以回看我上次的 Recap)
奈何,它家就是出了 Manus 的那家,然后重心转移了之后 Monica 就再也没什么新功能了,对于新模型的支持也不积极,高级模型还出了个积分制额外收费……
不过最重要的还是,ChatGPT Plus 已经成为了我的常订,而我对于 Claude、Gemini 等模型也没那么高的需求了
当然,如果你想要一个大而全又没那么贵的解决方案,Monica 还是一个很棒的选择(其实我现在依然用者它的浏览器插件,简单的问题随时划词问下体验也不错),如果你想订阅,欢迎使用我的邀请链接 https://monica.im/?ref=bryan
感觉 GitHub Copilot 已经跟不上现在这个时代了……
作为 Cursor + JetBrains 双持的我,前者自不用说,Copilot 对我而言毫无吸引力,而后者嘛
JetBrains 的 AI Assistant 已经自带了 AI 补全,将模式改成 Creative 后(我其实不太理解为啥它不是默认 - 可能是这个模式更耗费服务器资源?)体验十分棒。美中不足的是 NES(Next Edit Suggestions)还在 beta 且仅支持特定语言(没一个我在写的 = =),但 Copilot 虽然支持但也不咋地。。还不如免费的 Trae 呢😂
综上,👋 Copilot —— 一个我从内测就开始用的插件,一个几乎重塑了我的编码习惯的插件
我曾经是一个 Serverless 的「爱好者」,我觉得它是未来
奈何,现在越来越感觉到对于一个标准的应用来说,数据库是不可或缺的,而在 Serverless 的环境下,用户-边缘计算服务-数据库的延迟会变得十分明显,造成极差的用户体验
我目前几乎所有的服务都已迁移到 fly.io 和 zeabur —— 与之对应,Cloudflare Workers 也退订了
目前我已将 RSS 完全切换到了我自己写的 1Space,也因此退订了 Inoreader
不过,虽然退订了,但我没想到的是,过去的一年多 Inoreader 竟然获得了若干更新 —— 很神奇的事情,获取是换了个激进的产品经理吧,一个停止了七八年没大更新的服务突然更新/优化了不少
这不是我想退订,是官网登录系统直接挂了,给开发者发邮件/微信都不回!
哎,再没有通过 RSS 稳定订阅微信公众号的方式了
🤷 现在只能,已经不看微信公众号了
2025-08-01 08:21:00
2023 年 4 月我在 xlog 下写下了第一篇博客。xlog 真的是一个我很喜欢的博客平台,好看,对于 markdown 的第一方支持,不用考虑部署、图床等问题,自带 AI Summary、自带双语翻译,真的是一切只需要「写」即可,哦对,更重要的是,依赖于区块链技术,虽然你写的文章是在平台上的,但所有数据都依然属于你,且所有数据都是永久保存。
然而,两年过去了,xlog 这个平台虽然还在,但我认为它已经死了。GitHub 更新已基本停滞,没有人处理 issue、没有人审阅 pr,xlog 官网上充满了 spam,也没有人去管理社区。
其实这一切的原因都很好理解 —— xlog 的开发者 DIYGod 转去做 Folo 了。是啊,Folo 相比于 xlog 绝对是更有前景的项目,也更容易讲故事……
xlog 已被放弃,再加上 xlog 的母公司 RSS3 最近的动荡,我觉得是时候从 xlog 迁移走了。
我其实无比庆幸,两年前的时候我是打算把我所有博客迁移到 xlog 的。但是我的博客用的是 /yyyy/mm/dd/xxx 的 url 格式,而 xlog 并不支持这种格式,因而我一直是用 blog.singee.me 作为博客主域名 + articles.singee.me 作为 xlog 博客域名的,然后通过自动化将内容进行同步,且配置 xlog 博客的链接作为 Canonical URL。
这让我这次的迁移十分简单:将我原始博客的链接删掉、将 articles.singee.me 的原链接进行跳转即可!
嗯…… 唯一的副作用,通过 RSS 订阅我的博客的人应该会因为 id 变化了重新看一遍我的博客。其实这个是可以解决的,因为我之前 blog.singee.me 的 RSS 是通过 patch 了 hexo-generator-feed 实现的,完全可以特殊处理,但考虑到经历这个事件以后我应该不会再考虑这种「奇葩」的两处链接的形式了,所以我就把之前的 patch 回滚了,顺便增加一下我博客的曝光度 emm
anyway,博客又回来了,我又回到了原来的工作流:Notion 写作 + 同步到博客。已经这样写了两个博客了,一切都挺好,和两年前相比仿佛什么都没变 XD
2025-07-18 05:36:00
Python 的 pth 文件提供了在任何 Python 解释器执行前执行命令的能力,可以方便的执行一些初始化脚本
Python 解释器在启动时会自动 import site 模块(除非启动时指定 -S flag),而 site 模块有一个行为就是会寻找 site-packages 目录(最常用的场景就是我们安装包的目录)下的所有 .pth 文件并依次「执行」它。
需要注意的是, pth 实质上并不是脚本,它的定义是「path configuration file」,格式是每行一个「additional items (one per line) to be added to sys.path」,实际上,它每一行可以是下面的值之一:
import 开头的字符串:会被 exec() 解释执行sys.path 之中因此,利用第二个能力,我们实质上可以任意执行我们需要的脚本!
🗒️ 注:如果脚本执行出现异常,只会打印出错误而不会阻止解释器的继续执行
在 Python 3 的所有版本可用
相关 pth 文件会在 python 解释器启动时执行 —— 这包括执行脚本前、启动解释器前、执行任何用 python 写的程序前(如 pip)
当存在多个 pth 文件时,将采用字母序的方式依次执行 —— 一个值得强调的点是,python 的 virtualenv 就是通过 _virtualenv.pth 执行的 —— 这意味着,我们应当注意 pth 的命名以让其确保在 venv 之前或之后运行