Python中sys.executable和sys.version不匹配

10 投票
3 回答
7525 浏览
提问于 2025-04-17 21:12

系统里安装了两个Python解释器:

[user@localhost ~]$ /usr/bin/python -V && /usr/local/bin/python -V
Python 2.4.3
Python 2.7.6

Sudo在运行每个命令时,会这样修改PATH

[user@localhost ~]$ env | grep PATH && sudo env | grep PATH
PATH=/usr/kerberos/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/home/user/bin
PATH=/usr/bin:/bin

我运行了一个测试脚本:

[user@localhost ~]$ cat what_python.py
#!/usr/bin/env python

import sys
print sys.executable
print sys.version
[user@localhost ~]$ sudo python what_python.py
/usr/bin/python
2.7.6 (default, Feb 27 2014, 17:05:07) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-54)]

结果在sys.executable里得到了Python 2.4.3的路径,而在sys.version里显示的是2.7.6的版本。很明显,sys.executablesys.version的内容不一致。考虑到sudo是如何修改PATH的,我能理解sys.executable的值。但是,为什么sys.version显示的是2.7.6而不是2.4.3呢?这两个版本应该是匹配的,因为sys.executable报告的路径是usr/bin/python

这是我之前问题的后续,链接是 Sudo修改PATH,但执行的是同一个二进制文件

3 个回答

0

每次你启动 Python 解释器时,系统会去 /usr/bin/python 这个位置执行它(你可以试试:python -c "import os; print(os.environ['_'])")。

你可以看到,执行 ln -l | grep python 命令后,/usr/bin/python 实际上是指向 Python 解释器的一个软链接。

我做的步骤是:

  1. 安装最新版本的 Python(去 Python 的官网,下载最新的代码,然后运行 configure && make && make install)。
  2. 检查这个最新版本的可执行文件的位置。
  3. 删除 /usr/bin/python 的软链接(这需要管理员权限,使用 sudo)。
  4. 运行 ln -s <最新版本可执行文件的位置> /usr/bin/python (很可能也需要 sudo 权限)。
  5. 在命令行中执行 Python。
  6. 输入 import sys。

然后输入 sys.version,这样你就能看到最新的版本号了。

1

我觉得 /usr/local/bin/python 是正在运行的程序。版本信息几乎肯定是写在 python 里面的,所以出错的可能性很小。看看 sys.executable 的文档:

sys.executable

这是一个字符串,给出了 Python 解释器的可执行文件的绝对路径,适用于那些有意义的系统。如果 Python 无法获取它的可执行文件的真实路径,sys.executable 会是一个空字符串或 None。

这说明 python 可能无法获取这个路径,意味着它在使用 PATH 来查找可执行文件,而这个 PATH 是由 sudo 设置的(根据我之前的回答,这个路径和它用来找到可执行文件的路径是不一样的)。

要确认这一点,唯一的方法就是深入研究 Python 的实现,但一般来说,我认为版本信息更值得信赖。不过,sudo 是通过 execve 来执行命令的(至少根据 man 页面)。你必须给 execve 指定可执行文件的完整路径(某些 exec 的变种会自己查找 PATH,但这个不会)。所以对于 python 来说,填充 sys.executable 应该是没问题的。

我不知道有没有办法获取到实际的 argv[0] 对于 python 解释器(sys.argv[0] 总是脚本的名字或 -c),但这会很有趣。如果它是 /usr/local/bin/python,那就说明 python 有个 bug。

我觉得最好的办法就是在 /etc/sudoers 中设置 secure_path,希望这样能得到一些一致性。

更新

其实 execve 接受一个可执行文件路径的参数和一个 argv 数组,所以 argv[0] 不一定是 /usr/local/bin/python。不过你仍然可以通过写一个脚本来找出它:

import time
time.sleep(60)

然后运行它,使用 ps 来获取完整的参数:

sudo python sleep.py &
ps -o args= -C python

另外,为了确认正在运行的是哪个 python,你可以在程序运行时这样做:

sudo ls -l /proc/PID/exe

6

其实,@Graeme 和 @twalberg 说的基本上是对的。我一开始不太相信 Python 会用这么简单(甚至有点傻)的方式来找到自己,但事实就是这样。

Python 的 sys 模块是在 Python/sysmodule.c 这个文件里实现的。从 2.7.6 版本开始,sys.executable 在第 1422 行被设置为:

 SET_SYS_FROM_STRING("executable",
                     PyString_FromString(Py_GetProgramFullPath()));

Py_GetProgramFullPath() 这个函数是在 Modules/getpath.c 文件中定义的,从第 701 行开始:

char *
Py_GetProgramFullPath(void)
{
    if (!module_search_path)
        calculate_path();
    return progpath;
}

同一个文件里还有一个叫 calcuate_path() 的函数,里面有以下的 注释

/* If there is no slash in the argv0 path, then we have to
 * assume python is on the user's $PATH, since there's no
 * other way to find a directory to start the search from.  If
 * $PATH isn't exported, you lose.
 */

在我的例子中,如果第一个在 $PATH 中的 Python 跟正在运行的 Python 不一样,那就会出问题。

关于计算解释器可执行文件位置的过程,可以在 getpath.c 文件的 开头 找到更多信息:

在进行任何搜索之前,首先确定可执行文件的位置。如果 argv[0] 中有一个或多个斜杠,就直接使用它。否则,它必须是从 shell 的路径中调用的,所以我们会在 $PATH 中搜索这个可执行文件。如果在 $PATH 中找不到这个可执行文件(或者没有 $PATH 环境变量),就会使用原始的 argv[0] 字符串。

接下来,会检查可执行文件的位置是否是一个符号链接。如果是,就会跟踪这个链接(如果找到相对路径名,会正确解释)并使用链接目标的目录。

让我们做几个测试来验证上述内容:

如果 argv[0] 中有一个或多个斜杠,就直接使用它。

[user@localhost ~]$ sudo /usr/local/bin/python what_python.py
/usr/local/bin/python
2.7.6 (default, Feb 27 2014, 17:05:07) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-54)]

好的。

如果在 $PATH 中找不到可执行文件(或者没有 $PATH 环境变量),就会使用原始的 argv[0] 字符串。

[user@localhost ~]$ sudo PATH= python what_python.py
<empty line>
2.7.6 (default, Feb 27 2014, 17:05:07) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-54)]

错了。在这种情况下,sys 模块的 文档 是正确的 – 如果 Python 无法获取其可执行文件的真实路径,sys.executable 将是一个空字符串或 None。

让我们看看把 Python 的二进制文件位置重新加回 PATH(在 sudo 移除后)是否能 解决 这个问题:

[user@localhost ~]$ sudo PATH=$PATH python what_python.py
/usr/local/bin/python
2.7.6 (default, Feb 27 2014, 17:05:07) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-54)]

确实解决了。

相关内容:

  • Python 问题 7774 – sys.executable: 如果第一个命令参数被修改,位置错误。
  • Python 问题 10835 – sys.executable 默认和 altinstall。
  • python-dev 邮件列表 讨论 – 关于 sys.executable 更严格的定义。
  • Stackoverflow 问题 – 如何在 C 中找到可执行文件的位置。

撰写回答