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

5 投票
4 回答
8146 浏览
提问于 2025-04-17 19:26

我有一个用Python写的软件,它包含一个配置文件和一个手册页。为了安装这些文件,我在我的 setup.py 文件里写了以下代码(具体内容可以参考 这个链接):

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

当我以管理员身份用 python setup.py install 安装软件时,这个方法运行得很好。但是在虚拟环境中就不行了,因为用户没有权限写入 /etc/usr/share/man 这些地方。

那么,解决这个问题的最佳方法是什么呢?是检查当前环境中的 VIRTUAL_ENV 变量,然后干脆不安装这些文件吗?软件会在本地目录中寻找 foo.conf,所以这应该没问题。用户可能会缺少手册页,但实际上也没有好的办法来安装它,因为 man 命令不会在虚拟环境附近寻找它。

4 个回答

1

我找到的一个非常简单的答案就是,运行以下代码:

pip -V # (Uppercase V)
1

很多使用Python的人错误地认为,虚拟环境是通过$PATH的修改来定义的,这些修改是由bin/activate脚本完成的。但其实并不是这样——activate脚本只是一个方便的工具,它对Python本身没有任何影响,它只是让你更容易地访问虚拟环境。

一个Python虚拟环境是通过在其中运行时sys.path的变化来定义的,还有Python自动发现库目录的方式,这个发现是相对于python[3[.n]]解释器的文件系统位置的。所有的进程环境变量,包括$PATH$VIRTUAL_ENV,在Python的虚拟环境中都是不相关的。任何使用环境变量的检测其实只是检查activate脚本是否被运行过。正如马丁所指出的,activate脚本并不是在虚拟环境中运行的前提条件。

如果你有一个虚拟环境在/home/me/my_venv/,运行/home/me/my_venv/bin/activate然后再运行python3,你会得到一个在那个虚拟环境中运行的交互式命令行。但你也可以直接运行/home/me/my_venv/bin/python3而不需要先运行activate。虽然$PATH会不同,$VIRTUAL_ENV也不会被设置,但用这两种方法得到的sys.path是完全一样的。(试试看吧!)

所以,检测虚拟环境的关键在于当解释器从一个不同于其安装路径的位置运行时,sys.path是如何变化的,这一切都始于修改后的sys.prefix

sys.real_prefixvirtualenv包曾经设置的东西。它引起了一些问题,讽刺的是因为venv没有检测到它在virtualenv中运行,所以virtualenv在其v20重写中去掉了sys.real_prefix。实际上,从v20开始,virtualenv在创建虚拟环境时使用标准库中的venv模块(在所有当前支持的Python版本中都是可用的)。

因此,对于所有当前支持的Python版本(3.7–3.11),以下方法可以有效地检测虚拟环境,而不依赖于$PATH$VIRTUAL_ENV。(我保留了sys.real_prefix的检查,尽管它应该很少成功,并且会表明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_prefixsys.prefix是相同的——它们指向运行时的安装目录。

例如,Windows上的ActiveState Python会在系统上创建的每个项目实例中安装一个捆绑的解释器二进制文件。那个运行时的sys.prefix可能像c:\users\$name\appdata\local\activestate\cache\$random,而sys.base_prefix也是一样。一些Node.js包也做类似的事情,使用捆绑的Python安装。

在这些情况下,sys.path也会完全位于sys.prefix路径下,这使得该安装在技术上根本不是一个虚拟环境。它仅仅是一个非系统的、捆绑的Python运行时环境,与任何系统运行时没有连接或依赖。这些环境更难以可靠地检测,并且在这些条件下编写代码假设是一件更棘手的事情。

12

注意:我最初在很多年前写了这个回答,当时讨论的是一个非常旧的虚拟环境工具(virtualenv)和Python 2.7,而不是Python 3。

我不确定下面的回答在当前版本的Python和virtualenv中是否仍然准确。我预计其中至少有部分内容在考虑当前软件行为时可能会误导。


最终看来,你的问题实际上是关于如何判断当前运行的Python是否在一个虚拟环境中。要搞清楚这一点,我们必须了解virtualenv是如何工作的。

当你在虚拟环境中运行activate脚本时,它会做两件事情:

  • 它会更新PATH环境变量,将虚拟环境的bin目录包含进去,这样当你运行python时,就会运行虚拟环境中的Python。
  • 它会设置一个变量VIRTUAL_ENV,这样activate脚本本身就可以跟踪激活状态。

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

你可以通过导入sys模块并查看sys.prefix来确定系统的前缀。然而,当虚拟环境没有被激活时,依赖这个值并不好,因为这是Python在构建时的设置,可以很容易地自定义,并且在不同的平台上会有所不同。

不过,Python在虚拟环境前缀和其编译时前缀运行时确实有一个小的运行时差异: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不会被设置,但用户仍然可以写入他的Pythonlib目录,可能并不能写入/etc/usr/share/man

  • 同样,在某些系统上,管理员可能已经授予某个应用开发者组对/usr/lib/python2.7的组写权限,以便他们可以安装Python模块,但没有授予他们对其他系统文件的写权限。

所以我认为最终你能做的最好方法是使用启发式方法,可能更好的是避免在data_files中使用绝对路径,特别是对于你希望在虚拟环境中使用的模块。一个折中的办法是将你的模块分成两个发行版,一个代表可本地化的源文件,另一个代表系统范围的配置以使其运行。后者可以依赖于前者,这样用户仍然可以轻松安装,但使用virtualenv的用户可以选择直接使用前者。

撰写回答