当前位置:首页>python>达梦dmPython安装后,为什么 python -m 失败?一个第三方库导致的 启动链路失败

达梦dmPython安装后,为什么 python -m 失败?一个第三方库导致的 启动链路失败

  • 2026-09-11 21:30:39
达梦dmPython安装后,为什么 python -m 失败?一个第三方库导致的 启动链路失败

达梦dmPython安装后,为什么 python -m 失败?一个第三方库导致的 Python 启动链路失败

这个问题我当时绕了很久。

表面现象是:

$ python3 -m pip install xxx

/usr/bin/python3: No module named install

或者:

$ python3 -m pip -V

/usr/bin/python3: No module named -V

看起来像是:

python -m 的参数解析坏了

但真正的问题不是 pip,也不是 -m 本身。

真正的问题是:

dmPython 安装时带了一个 `.pth` 文件,这个 `.pth` 在 Python 解释器启动中途执行了 `os.execve`,把 `-m` 模式下本来就不完整的 `sys.argv` 又拿去重拼了一次。

这篇真正想讲清楚的,不只是“怎么修”。

而是:

• .pth 为什么会影响 python -m

• 解释器为什么会把模块名单独拿走

• 为什么最后只吃掉了一个参数,而不是把后面的全吃掉

一、现场现象:import dmPython 正常,但 python -m pip 坏了

项目背景是信创迁移。

业务系统从 MySQL 迁到达梦,安装 dmPython 后:

• import dmPython 正常

• python3 -m pip 突然报错

也就是说,坏掉的不是 dmPython 本身能不能 import。

坏掉的是:

整个 python -m 启动链路

这就说明问题大概率不在业务模块,而是在:

解释器初始化阶段

二、strace 最先把问题指到了 .pth

先用 strace 跟踪:

$ strace -f python3 -m pip 2>&1 | grep pth

openat(AT_FDCWD, "/site-packages/dmPython.pth", O_RDONLY) = 3

然后看文件内容:

$ cat /site-packages/dmPython.pth

import dmdpi

继续看 dmdpi.py,发现关键逻辑大概是:

import os, sys

if 'LD_LIBRARY_PATH' not in os.environ:

os.environ['LD_LIBRARY_PATH']= dpi_path

ifsys.argv[0] == '':

os.execve(sys.executable,[sys.executable, ...], os.environ)

else:

os.execve(sys.executable,[sys.executable] + sys.argv, os.environ)

到这里,问题其实已经比较清楚了:

1. .pth 会在解释器启动时自动执行

2. 它触发了 import dmdpi

3. dmdpi.py 又在启动中途执行了 os.execve

也就是说,Python 解释器本来正在启动,结果中途被人用一份新的命令行又重新启动了一次。

三、关键原理:python -m 里的模块名,本来就不是普通参数

这里最容易误解。

很多人会下意识觉得:

python3 -m pip install xxx

不就是一串普通参数吗?

其实不是。

这里的 pip 不是“传给程序的普通参数”。

它是:

解释器自己接下来要执行的目标。

你可以把 Python 启动时先要决定的事情理解成:

• 我是执行脚本文件?

• 还是执行 -c 后面的代码?

• 还是执行 -m 后面的模块?

• 还是进入交互式?

所以在:

python3 -m pip install xxx

这条命令里,解释器会先把它拆成三部分:

• 解释器选项:-m

• 执行目标:pip

• 传给 pip 的参数:install xxx

这就是为什么解释器必须先把 pip 单独解析出来。

因为如果不先拿出来,解释器后面根本不知道:

到底该运行哪个模块

也不知道之后的 install xxx 应该交给谁。

所以 -m 的语法,本质上就是:

-m 只消费后面紧跟着的 1 个参数,作为模块名

剩下的参数保留给这个模块自己使用

这也是为什么后来“只错位了一个参数”。

因为解释器本来就只会单独拿走一个:

pip

后面的 install xxx 本来就是留给 pip 的,不归解释器消费。

四、解释器真正的启动顺序,问题就卡在这里

python -m 的关键不只是“拿走模块名”。

还在于:

模块真正执行,不是发生在解释器最开始。

大致顺序可以先粗暴理解成这样:

1. C 层解析命令行

2. 发现是 -m,把模块名 pip 单独保存起来

3. 初始化解释器

4. import site

5. site 处理 .pth

6. .pth 触发 import dmdpi

7. dmdpi.py 执行 os.execve

8. 如果没被打断,最后才由 runpy 把保存下来的 pip 当作 __main__ 跑起来

这里最关键的是第 2 步和第 8 步。

模块名 pip 已经被解释器保存下来了。

但在 site/.pth 执行的时候,pip 还没真正开始跑。

所以这时候如果你去看 sys.argv,你看到的不是完整命令,而是:

['-m', 'install', 'xxx']

也就是说:

• pip 已经被解释器拿走

• install xxx 还留在 sys.argv 里,准备之后交给 pip

这不是异常。

这就是 -m 模式在启动中途本来的样子。

五、问题就出在:dmdpi.py 用这份“中途状态”的 sys.argv 重拼命令

现在再回来看这段逻辑:

os.execve(sys.executable, [sys.executable] + sys.argv, os.environ)

它的问题不是单纯“重启 Python”。

而是:

它拿来重启的参数列表,根本不是原始命令行,而是解释器启动到一半时的 `sys.argv`。

原始命令本来是:

python3 -m pip install xxx

但 site 阶段的 sys.argv 已经变成:

['-m', 'install', 'xxx']

于是它重拼出来的就变成了:

python3 -m install xxx

这时候解释器第二次启动,再按 -m 的规则解析:

• -m 还是解释器选项

• install 被当成新的模块名

• xxx 成了传给 install 的参数

如果你原来跑的是:

python3 -m pip -V

那第二次重拼后就会变成:

python3 -m -V

于是错误就成了:

No module named -V

所以这里不是“后面的参数为什么没被吃掉”。

而是:

解释器按规则本来就只会消费 `-m` 后面的一个参数作为模块名。原来的 `pip` 丢了以后,后面紧跟着的那个参数就自动顶上来,成了新的模块名。

六、怎么验证 site 阶段看到的 sys.argv 确实已经缺了模块名?

我当时的验证方式很直接。

写一个 usercustomize.py:

import os

import sys

os.environ["ARGV_DEBUG"] = str(sys.argv)

然后执行:

$ python3 -m pip --version

再看结果:

['-m', '--version']

这就证明了两件事:

1. pip 确实已经不在 sys.argv 里

2. site 阶段拿到的 sys.argv 本来就是“少了模块名”的中间态

所以 dmdpi.py 再拿它去 execve,天然就会错位。

七、为什么 import dmPython 本身又没事?

因为这次事故里,真正有问题的不是:

dmPython.so 能不能找到自己的依赖库

而是:

它为了设置动态库路径,在解释器启动阶段额外搞了一次 execve

而实际排下来,dmPython.so 自己已经能通过内置的 RPATH 找到依赖库。

也就是说,根本不需要:

• .pth 在 site 阶段自动 import dmdpi

• dmdpi.py 去改 LD_LIBRARY_PATH

• 再 os.execve 把解释器重新拉起一次

所以 import dmPython 没问题,不代表这套启动链是安全的。

只能说明:

真正出事的是解释器启动流程,不是模块导入结果本身

八、修复很简单,问题却很典型

修复方式其实很直接:

rm /site-packages/dmPython.pth

删掉以后:

• python3 -m pip 恢复正常

• import dmPython 仍然正常

后面达梦新版也改成了 wheel 形式,不再依赖:

• .pth

• dmdpi.py

• 启动阶段 os.execve

这说明旧方案的问题不在于“写法不优雅”。

而在于:

它把解释器还没启动完的中间状态,当成了可以无脑复用的最终命令行。

最后一句

这次问题的根因,不是 pip 坏了,也不是 `-m` 机制坏了,而是 `.pth` 在 `site` 初始化阶段执行了 `os.execve`,错误地用中间态 `sys.argv` 重建了命令行。`python -m` 的模块名本来就会被解释器单独拿走,后面的参数本来就是留给模块自己的;一旦你在这个阶段重启解释器,原来的模块名丢了,后面紧跟着的那个参数就会自动顶上来,变成新的模块名。

最新文章

随机文章