Python中sys.executable和sys.version之间的不匹配问题

10

已安装两个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而不是与sys.executable报告的usr/bin/python路径匹配的版本2.4.3呢?这是我问题的后续:Sudo changes PATH, yet executes the same binary

你可以使用 ls -l /usr/bin/python 命令,看看以超级用户身份登录时是否指向了不同的可执行文件?虽然很奇怪,但这是有可能的。 - Corley Brigman
@CorleyBrigman [user@localhost ~]$ sudo ls -l /usr/bin/python 的输出结果是 -rwxr-xr-x 2 root root 8304 Oct 23 2012 /usr/bin/python - Piotr Dobrogost
@twalberg (...) 这似乎可能破坏了一些Python的假设... - 我不这么认为,因为a)我怀疑sudo会隐藏有关其运行进程的可执行文件位置的信息; b)我不认为Python必须对其可执行文件的位置进行任何假设,因为在正常情况下(包括sudo),每个进程都应该知道其可执行文件的位置,即该信息没有被隐藏。 - Piotr Dobrogost
@PiotrDobrogost 我的意思是sys.executable似乎在搜索当前路径时解析了argv[0](或者可能是因为在这种情况下argv[0]只是python...),而不一定是由sudo启动的可执行文件,因为sudo在调用python之前改变了PATH,但它已经解析了要调用的python - twalberg
@twalberg,Piotr,Linux程序确定其可执行文件的正确方法是在/proc/self/exe下解析链接。如果python没有这样做,而是在搜索PATH,那么这就可以解释差异。您可以编写一个脚本来显示/proc/self/exe的目标和sys.executable,或者使用我下面的建议,在程序运行时外部检查。 - Graeme
显示剩余2条评论
3个回答

6

两位用户 @Graeme 和 @twalberg

Python无法检索到此内容的事实表明它正在进行自己的路径搜索(…)

是正确的。我不愿意相信Python会做一些像使用PATH这样简单(愚蠢?)的事情来定位自己,但这是真的。

sys模块在Python/sysmodule.c文件中实现。截止到2.7.6版本,在第1422行设置了sys.executable:

(…) 看起来sys.executable在当前路径中搜索,而不是解析argv[0](或者可能是因为这种情况下argv[0]就是Python本身...)(…)

 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中找到可执行文件的位置

1
我认为正在运行的可执行文件是 /usr/local/bin/python。版本字符串几乎肯定已编译到 python 中,因此很不可能出错。查看 sys.executable 的文档:

sys.executable

在这些系统上,它提供了 Python 解释器可执行二进制文件的绝对路径的字符串。如果 Python 无法检索其可执行文件的真实路径,则 sys.executable 将是一个空字符串或 None。

事实上,python 可能无法检索到这个路径,这表明它正在使用 PATH sudo 设置的自己的 PATH 搜索(正如我回答前一个问题时所述,这与用于找到可执行文件的路径不同)。
唯一确定的方法是查看Python实现,但通常我会说版本字符串更可信。另一方面,sudo使用execve执行命令(至少根据man页面)。您必须指定可执行文件的完整路径以供execve使用(某些exec变体执行它们自己的PATH搜索,但这个不会)。因此,python应该很容易填写sys.executable
我不知道是否有任何方法可以获取python解释器的实际argv [0]sys.argv [0]始终是脚本或-c的名称),但这将是有趣的。如果是/usr/local/bin/python,则这将是python中的错误。
我认为最好的做法就是在/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

当程序正在运行时。

0
每次启动Python解释器时,shell都会转到 /usr/bin/python 并执行它(尝试下一个命令:python -c "import os; print(os.environ['_'])")。
那么,正如您所看到的那样,/usr/bin/python 是指向Python解释器可执行文件的软链接(通过 ln -l | grep python 命令)。
我的做法是:
1. 安装最新版本的Python(访问Python网站,下载最新版本代码并运行 configure && make && make install 命令) 2. 检查此最新版本可执行文件的位置。 3. 删除 /usr/bin/python 软链接##(您需要 root 权限 sudo) 4. ln -s <最新Python版本可执行文件的位置> /usr/bin/python ##(很可能需要 sudo) 5. 从命令行执行Python。 6. import sys 7. sys.version ## 应该是最新版本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接