PEP 582 – Python local packages directory
- Author:
- Kushal Das <mail at kushaldas.in>, Steve Dower <steve.dower at python.org>, Donald Stufft <donald at stufft.io>, Nick Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 16-May-2018
- Python-Version:
- 3.12
摘要
此 PEP 提议向 Python 添加一种自动识别 __pypackages__
目录的机制,并优先导入安装在此位置的包,而不是用户或全局站点包。这将避免创建、激活或禁用“虚拟环境”的步骤。当出现时,Python 将使用脚本基目录中的 __pypackages__
。
动机
Python 虚拟环境已经成为社区中开发和教学工作流程的重要组成部分,但与此同时,它们也为许多人创造了进入的障碍。以下是人们在接触 Python(或第一次编程)时遇到的一些问题。
- 对于新手来说,虚拟环境是如何工作的是很多信息。解释它们需要花费大量额外的时间和精力。
- 不同的平台和 shell 环境需要不同的命令集来激活虚拟环境。任何研讨会或教学环境,如果人们的笔记本电脑上安装了不同的操作系统,都会在参与者中造成很多困惑。
- 虚拟环境需要在每个打开的终端上被激活。如果有人创建/打开新终端,默认情况下,该终端不会获得与之前激活的虚拟环境相同的环境。
基本原理
Python 是初学者友好的编程语言。但是,到目前为止,虚拟环境是新人学习过程中的主要时间。这个 PEP 并不是试图解决每个打包问题,而是专注于 90% 在学习过程中与虚拟环境作斗争的新人。创建新目录仍然比在不同平台上了解虚拟环境的细节容易得多。
PEP 的主要观点是,它并没有试图取代虚拟环境。如果需要虚拟环境的所有功能,他们应该使用合适的虚拟环境(例如,使用 venv
模块创建)。
规范
当 Python 二进制文件执行时,它尝试确定其前缀(存储在 sys.prefix
中),然后使用该前缀查找标准库和其他关键文件,并由 site
模块确定 site-package
目录的位置。目前要找到前缀(假设 PYTHONHOME
未设置),首先要遍历文件系统树,寻找表示标准库存在的标记文件(os.py
),如果没有找到,则返回到二进制文件中硬编码的构建时前缀。这个过程的结果是 sys.path
文件的内容 - Python 导入系统将搜索模块的位置列表。
这个 PEP 提议在这个过程中增加一个新步骤。如果在当前工作目录中找到 __pypackages__
目录,那么它将包含在 sys.path
目录中,位于当前工作目录之后,在系统 site-packages 之前。这样,如果 Python 可执行文件在给定的项目目录中启动,它将自动找到 __pypackages__
中的所有依赖项。
对于 Python 脚本,Python 将尝试在与脚本相同的目录中找到 __pypackages__
。如果找到了(以及其中的当前 Python 版本目录),那么它将被使用,否则 Python 将像当前一样运行。
如果任何包管理工具在当前工作目录中发现相同的 __pypackages__
目录,它将在那里安装任何包,并根据 Python 版本创建它。
使用源代码管理系统的项目可以包含 __pypackages__
目录(空目录或带有例如 .gitignore
这样的文件)。在重新检查源代码之后,可以使用像 pip
这样的工具将所需的依赖项直接安装到这个目录中。
但是,这并不能以类似的方式启用虚拟环境的所有功能。例如,如果项目有多个脚本,或者在不同的目录中有用于构建项目的辅助脚本,则应该优先使用普通的虚拟环境而不是 __pypackages__
。
示例
下面显示了项目目录结构示例,以及 Python 可执行文件和任何脚本的不同行为方式。
foo
__pypackages__
lib
python3.10
site-packages
bottle
myscript.py
/> python foo/myscript.py
sys.path[0] == 'foo'
sys.path[1] == 'foo/__pypackages__/lib/python3.10/site-packages/'
cd foo
foo> /usr/bin/ansible
#! /usr/bin/env python3
foo> python /usr/bin/ansible
foo> python myscript.py
foo> python
sys.path[0] == '.'
sys.path[1] == './__pypackages__/lib/python3.10/site-packages'
foo> python -m bottle
我们有名为 foo
的项目目录,其中有 __pypackages__
。将 bottle
安装在 __pypackages__/lib/python3.10/stie-packages/
中,并在项目目录中有 myscript.py
文件。通常使用的任何工具来安装 bottle
在那个位置。正如 sysconfig._INSTALL_SCHEMES['posix_prefix']
字典中提到的,这个实际的内部路径将依赖于 Python 实现名。
为了调用脚本,Python 将尝试在脚本所在的目录 [1],/usr/bin
中找到 __pypackages__
。在最后一个例子中,在 foo
目录中执行 /usr/bin/ansible
。在这两种情况下,它都不会在当前工作目录中使用 __pypackages__
。
类似地,如果调用第一个例子中的 myscript.py
,它将使用 foo
目录中的 __pypackages__
目录。
如果进入 foo
目录并启动 Python 可执行文件(解释器),它将在当前工作目录中找到 __pypackages__
目录,并在 sys.path
中使用它。如果我们尝试使用 -m
并使用模块,也会发生同样的情况。在我们的例子中,bottle
模块将在 __pypackages__
目录中找到。
以上两个例子只是使用当前工作目录中的 __pypackages__
的情况。
在另一个例子中,Python 类的 trainer 会说“今天我们要学习如何使用 Twisted!开始,请签出我们的示例项目,进入该目录,然后运行 python3 -m pip install twisted
。”
这将把 Twisted 安装到与 python3
分开的目录中。没有必要讨论虚拟环境、全局安装和用户安装等问题,因为默认情况下安装将是本地的。然后,trainer 可以一直告诉他们使用 python3
,而不需要任何激活步骤,等等。
安全注意事项
在执行 Python 脚本时,它不会考虑当前目录中的 __pypackages__
,相反,如果在脚本的同一路径中存在 __pypackages__
目录,则将使用该目录。
例如,如果从 /tmp
目录中执行 python /usr/share/myproject/fancy.py
,如果 /usr/share/myproject/
目录中有 __pypackages__
目录,它将被使用。/tmp
中任何潜在的 __pypackages__
目录都将被忽略。
这也意味着在执行脚本时不会扫描任何父目录。如果想要在 ~/bin/
目录中执行脚本,那么 __pypackages__
目录必须在 ~/bin/
目录中。
向后兼容性
这不会影响任何旧版本的 Python 实现。
对其他 Python 实现的影响
其他 Python 实现将需要复制解释器引导的新行为,包括定位 __pypackages__
目录并将其添加到 sys.path
刚好在站点包之前,如果它存在的话。
参考实现
pep582 是一个小脚本,它将启用 Cpython
在 PyPy
中的实现。
拒绝的想法
__pylocal__
和 python_modules
作为目录名。我们也不会重新实现虚拟环境的所有功能。
Copyright
This document has been placed in the public domain.
Source: https://github.com/python/peps/blob/main/pep-0582.rst
Last modified: 2023-01-29 19:18:01 GMT