如何在安装包时测试Python是否在虚拟环境中运行
我有一个用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 个回答
我找到的一个非常简单的答案就是,运行以下代码:
pip -V # (Uppercase V)
很多使用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_prefix
是virtualenv
包曾经设置的东西。它引起了一些问题,讽刺的是因为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_prefix
和sys.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运行时环境,与任何系统运行时没有连接或依赖。这些环境更难以可靠地检测,并且在这些条件下编写代码假设是一件更棘手的事情。
注意:我最初在很多年前写了这个回答,当时讨论的是一个非常旧的虚拟环境工具(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
的用户可以选择直接使用前者。