如何在安装软件包时测试Python是否正在运行虚拟环境中

5

我有一个Python软件,其中包括一个配置文件和一个man页。为了安装这些文件,我在我的setup.py中有以下代码(如http://docs.python.org/2/distutils/setupscript.html#installing-additional-files所述):

data_files = [('/etc/foo', ['foo.conf']), ('/usr/share/man/man1', ['foo.1'])]

这在以root身份使用python setup.py install安装软件时可以正常工作,但在虚拟环境中会失败,因为用户无权写入/etc/usr/share/man
最佳实践是什么?在当前环境中检查VIRTUAL_ENV,并根本不安装这些文件?软件将在本地目录中查找foo.conf,所以这应该没有问题。用户会错过man页,但无论如何都没有合理的方法来安装它,因为man不会在虚拟环境附近查找它。

3
我没有完整的答案来回答你的问题,但是检查 VIRTUAL_ENV 是不可靠的,因为一个人也可以明确地从 virtualenv 的 bin 目录运行 "python" 二进制文件来达到相同的效果。 - Martin Atkins
1
@MartinAtkins 确定吗?实际的二进制文件是相同的:md5sum /tmp/bar/bin/python /usr/bin/python b35da8492c4ddb3e3413411e574db63a /tmp/bar/bin/python b35da8492c4ddb3e3413411e574db63a /usr/bin/python。而我的理解是,我必须通过执行 bin/activate 来激活环境。 - zhenech
1
不,您不必激活环境。您可以轻松使用 cd /path/to/virtualenv 然后运行 bin/python - Martijn Pieters
VIRTUAL_ENV 环境变量本身并不被 Python 二进制文件使用;例如 VIRTUAL_ENV=/path/to/virtualenv /usr/bin/python 并不能启用它。 - Martijn Pieters
“activate”脚本最重要的功能是设置PATH环境变量,以便在运行“python”时,您将得到虚拟环境中的Python版本;VIRTUAL_ENV变量实际上只是为了virtualenv自己的记录。Virtualenv的技巧在于,Python通过查看其二进制文件安装位置上面的目录来确定其前缀,因此在/usr/bin中,它将是/usr,但在您的虚拟环境中,它是环境根目录。 - Martin Atkins
4个回答

11

最终看起来你的问题实际上是如何检测正在运行的Python是否在虚拟环境中。要深入了解这一点,我们必须了解 virtualenv 的工作原理。

当你在虚拟环境中运行 activate 脚本时,它会执行两个操作:

  • 更新 PATH 环境变量,包括虚拟环境中的 bin 目录,以便在运行 python 时会运行虚拟环境中的二进制文件。
  • 设置一个名为 VIRTUAL_ENV 的变量,以便 activate 脚本本身可以跟踪激活状态。

直接从虚拟环境中运行 python 是完全可以接受的,在运行时 python 并不使用 VIRTUAL_ENV 变量。它会确定包含正在运行的 python 二进制文件的目录,并使用其父目录作为“前缀”。

您可以通过导入 sys 模块并查阅 sys.prefix 来确定系统的前缀。但是,当虚拟环境没有被激活时,依赖于此值是一个坏主意。这是 Python 的编译时设置,可以轻松自定义,并且在不同平台上肯定会有所不同。

然而,当 Python 从虚拟环境前缀 vs. 编译时的默认前缀运行时,它确实有 一点微小的运行时差异: sys 包有一个额外的变量 real_prefix,返回编译到 Python 二进制文件中的前缀。因此,可以使用此方法来识别 Python 是否在非默认位置运行,这很可能意味着它正在从虚拟环境中运行:

import sys

if getattr(sys, "real_prefix", None) is not None:
    print "Maybe in a virtualenv"
else:
    print "Probably not in a virtualenv"
然而,即使这也不是一个精确的科学方法。这只是告诉你,在编译时指定的位置上没有找到Python二进制文件。它不能告诉你当前用户是否有写入 /usr/share/man 的访问权限 - 在某些(可能是边缘)情况下,这种方法可能无法给出正确的答案:
- 如果用户在自己的主目录中从源代码编译了自己的Python,并且其编译前缀为 /home/johnd/local-python,那么real_prefix将不会设置,但用户仍然具有写入 Python lib 目录的访问权限,而且可能没有写入 /etc 或 /usr/share/man 的访问权限。 - 同样,对于某些系统,管理员可能已经授予某个应用程序开发人员组对 /usr/lib/python2.7 的群组写权限,以便他们可以安装Python模块,但未授予他们对其他系统文件的写入访问权限。
因此,我认为你最好能做的就只是一种启发式方法,可能更好的方法是避免在 data_files 中使用绝对路径,对于任何你希望在虚拟环境中使用的模块。一个折衷办法可能是将你的模块分成两个分发版本,一个表示本地化的源文件,另一个表示系统范围内的配置以使其运行。后者可以依赖于前者,以便用户仍然可以轻松安装它,但是那些使用 virtualenv 的用户可以直接使用前者作为选项。

1
我发现对于这个问题,一个非常简单的答案是运行以下命令:

pip -V # (Uppercase V)

0
许多Python用户错误地认为是由bin/activate脚本执行的对$PATH的修改定义了虚拟环境。但这并不是真的——activate脚本只是一个方便的工具,对Python没有任何影响,它仅仅配置你的环境以更轻松地访问venv。
Python虚拟环境是由运行时在其中发生的对sys.path的更改以及Python自动发现库目录相对于python[3[.n]]解释器可执行文件的文件系统位置而定义的。所有进程环境变量,包括$PATH$VIRTUAL_ENV,对Python内部的虚拟环境都是无关紧要的。任何使用环境变量进行检测的操作实际上只是检查是否已运行activate脚本。正如Martin所指出的那样,activate脚本并不是在venv中运行的先决条件。
如果您在/home/me/my_venv/中有一个虚拟环境,那么先运行/home/me/my_venv/bin/activate,然后再运行python3将会让您在该虚拟环境内运行REPL。但是不使用activate也可以通过运行/home/me/my_venv/bin/python3来实现。两种方法的sys.path都是相同的,但是$PATH会有所不同,$VIRTUAL_ENV也不会被设置。(试一下就知道!)
因此,检测venv的关键在于当解释器从其安装路径以外的位置运行时,sys.path如何更改,这始于修改后的sys.prefix

sys.real_prefixvirtualenv 包用来设置的。具有讽刺意味的是,这会引起问题,因为venv 未能检测到它正在运行在virtualenv内部,所以virtualenv 在其 v20重写中删除了 sys.real_path。事实上,从v20开始,如果可用,virtualenv 使用标准库的venv模块创建其虚拟环境。(在当前支持的所有Python版本中都是可用的。)

因此,对于所有当前支持的Python版本(3.7-3.11),以下内容将有效地检测虚拟环境,而不依赖于$PATH$VIRTUAL_ENV。(我保留了sys.real_path检查,即使它很少成功并且表示非常过时的virtualenv包。)

def in_venv() -> bool:
    """Determine whether Python is running from a venv."""
    import sys
    if hasattr(sys, 'real_prefix'):
        return True
    pfx = getattr(sys, 'base_prefix', sys.prefix)
    return pfx != sys.prefix

然而,这仍然无法检测到一种非常特定的“虚拟”环境:已完全与系统安装分离的Python解释器分发。它们不会被注册为虚拟环境,因为sys.base_path将与sys.prefix相同-它们都将指向运行时的安装目录。

例如,在Windows上,ActiveState Python将在创建在系统上的每个项目实例中安装一个供应商提供的解释器二进制文件。该运行时可能具有类似于c:\users\$name\appdata\local\activestate\cache\$randomsys.prefix,并且sys.base_prefix将是相同的。一些Node.js软件包也会使用供应商提供的Python安装程序执行类似的操作。

在这些情况下,sys.path也将完全位于sys.prefix路径下,使得安装技术上根本不是一个虚拟环境。它只是一个非系统的、供应商提供的Python运行时环境,与任何系统运行时没有连接或依赖关系。这些环境要可靠地检测起来要困难得多,并且编码假设在这些条件下要做什么是一个更加棘手的问题。


0

将问题缩小到标题:

"如何在软件包安装期间测试Python是否正在从virtualenv运行"

由于已经超过7年,所以被接受的答案对我没有用(在Ubuntu 18.04上使用python 3.6.9)。

当我在虚拟环境中(已激活并正在使用)时,它未能检测到。更具体地说,getattr(sys,“real_prefix”,None)返回“/usr”,错误地得出我没有从虚拟环境中运行。

作为问题本身建议的替代解决方案(1)可以很好地工作。尽管上面有反对意见认为在非常规情况下可能不可靠,但它是简单明了的。

将其保存到文件is-in-venv中并使其可执行,然后可以从shell中调用if is-in-venv; then ...; else ...; fi

#!/usr/bin/env python3

import os
import sys

venvdir = os.getenv('VIRTUAL_ENV')

if venvdir and os.path.isdir(venvdir):
    print("python3 virtual-env detected: %s" % venvdir)
    sys.exit(0)
else:
    print("python3 not in a virtual env")
    sys.exit(1)

(1) 为了那些在寻找 Python3 的可行解决方案的潜在未来用户的利益,时间大约在2020年左右。


那只是愚蠢的。如果你要使用_shell_来确定环境中是否设置了VIRTUAL_ENV,你肯定不需要Python来为你决定。只需使用if [ "x$VIRTUAL_ENV" != "x" ] - FeRD
或者,如果您仍然想执行目录测试,只需 if [ -d "$VIRTUAL_ENV" ](这将DTRT并在环境中未设置 $VIRTUAL_ENV 时测试为false)。 - FeRD
OP的问题是“如何测试Python是否正在从virtualenv运行...”,因此用于检查的Python代码适当地回答了这个问题。我添加shebang和sys.exit()的唯一原因是为了使代码完全自包含和可重复作为一个单元,除了能够直接从Python调用它之外。显然,您可以从支持它的任何语言中检查环境变量的存在。干杯。 - arielf

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