PEP 514 – Python 在 Windows 注册表中的注册
- 作者:
- Steve Dower <steve.dower at python.org>
- BDFL 委托:
- Paul Moore <p.f.moore at gmail.com>
- 状态:
- 活跃
- 类型:
- 信息性
- 创建日期:
- 2016年2月2日
- 发布历史:
- 2016年2月2日, 2016年3月1日, 2016年7月18日
- 决议:
- Python-Dev 消息
摘要
本 PEP 定义了 Python 注册表键的模式,以允许第三方安装程序注册其安装,并允许工具和应用程序检测并正确显示用户机器上的所有 Python 环境。本 PEP 未提出对 Python 的任何实现更改。
除非 Python 环境希望被外部工具自动发现,否则不需要注册。由于这仅与 Windows 相关,因此这些工具预计主要是 GUI 应用程序。但是,控制台应用程序也可以利用注册信息。本 PEP 涵盖了可能提供的信息,但这些信息的实际呈现和使用则留给工具设计者。
该模式与官方安装程序自 Python 2.5 以来使用的注册表值相匹配,并且解析行为与官方 Python 版本的行为相匹配。提供了一些向后兼容性规则,以确保工具能够正确检测未注册完整信息的 CPython 版本。
动机
当安装在 Windows 上时,官方 Python 安装程序会创建一个注册表键,供其他应用程序发现和检测。这允许安装程序或 IDE 等工具自动检测并显示用户的 Python 安装。例如,PEP 397 py.exe 启动器以及 PyCharm 和 Visual Studio 等编辑器已经在使用此信息。
第三方安装程序(例如发行版使用的安装程序)通常会出于相同目的创建相同的键。大多数使用注册表检测 Python 安装的工具只检查官方安装程序使用的键。因此,希望可被发现的第三方安装将覆盖这些值,这通常导致用户“丢失”其原始 Python 安装。
通过描述注册表键的布局,允许第三方安装程序唯一注册自己,并为工具开发人员提供发现所有可用 Python 安装的指南,应可防止这些冲突。我们还借此机会添加了一些众所周知的元数据,以便可以向用户呈现更多信息。
定义
“注册表键”相当于注册表中的文件系统路径。每个键可能包含“子键”(嵌套在键内的键)和“值”(附加到键的命名和类型属性)。这些在 Windows 上用于存储设置,与包含配置文件的目录的工作方式非常相似。
HKEY_CURRENT_USER 是当前登录用户的设置根目录,该用户通常可以读取和写入此根目录下的所有设置。
HKEY_LOCAL_MACHINE 是所有用户的设置根目录。通常,任何用户都可以读取这些设置,但只有管理员才能修改它们。通常,HKEY_CURRENT_USER 下的值优先于 HKEY_LOCAL_MACHINE 中的值。
在 64 位 Windows 上,HKEY_LOCAL_MACHINE\Software\Wow6432Node 是一个特殊键,32 位进程透明地读取和写入此键,而不是直接访问 Software 键。
有关 Windows 注册表重定向的更多文档可从 MSDN Library [1] 获得。
结构
我们认为机器上存在单个 Python 环境集合,其中该集合对于机器的每个用户可能不同。根据每个环境的安装选项,有三个潜在的注册表位置可以存储该集合
HKEY_CURRENT_USER\Software\Python\<Company>\<Tag>
HKEY_LOCAL_MACHINE\Software\Python\<Company>\<Tag>
HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python\<Company>\<Tag>
官方 Python 版本将 PythonCore 用于 Company,并将 sys.winver 的值用于 Tag。Company PyLauncher 已保留。其他注册环境可以使用任何值用于 Company 和 Tag。本文档稍后会提出建议。
Company-Tag 对不区分大小写,并唯一标识每个环境。根据工具的目的和预期用途,有两种建议的方法来解决 Company-Tag 对之间的冲突。
列出所有已安装环境的工具可以选择包含那些即使 Company-Tag 对匹配的环境。它们应确保用户可以轻松识别注册是按用户还是按机器进行的,以及哪个注册具有更高的优先级。
旨在根据 Company-Tag 对从所有已注册环境中选择单个已安装环境的工具(例如 py.exe 启动器)应始终选择注册在 HKEY_CURRENT_USER 中的环境,而不是 HKEY_LOCAL_MACHINE 中匹配的环境。
HKEY_LOCAL_MACHINE\Software\Python 和 HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python 之间的冲突只应在解释器的 64 位和 32 位版本具有相同标签时发生。在这种情况下,工具应选择对其用途更合适的那个。
如果工具能够根据所提供的信息(或缺乏信息)确定它不能使用已注册的环境,则没有义务将其呈现给用户。
除了在向后兼容性部分中讨论的以外,Company 和 Tag 值被认为是工具不透明的,不应从文本中推断出有关解释器的任何信息。但是,某些工具可能会向用户显示 Company 和 Tag 值,因此理想情况下,Tag 应该能够帮助用户识别关联的环境。
除非 Python 环境希望被外部工具自动发现,否则不需要注册。
向后兼容性
Python 3.4 及更早版本在 sys.winver 中不区分 32 位和 64 位构建。因此,在此方案下不可能同时安装 32 位和 64 位解释器的有效并行安装,因为它会导致标签重复。
为了确保向后兼容性,应用程序应将以下两个注册表键下列出的环境视为不同的,即使标签匹配
HKEY_LOCAL_MACHINE\Software\Python\PythonCore\<Tag>
HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python\PythonCore\<Tag>
列在 HKEY_CURRENT_USER 下的环境可以视为与上述两个键不同,这可能导致使用相同标签发现三个环境。或者,工具可以确定按用户环境是 64 位还是 32 位,并优先于按机器环境,从而最多发现两个环境。
在 3.5 之前,当 64 位和 32 位版本的 Python 为当前用户安装时,无法检测它们的并行安装。Python 3.5 及更高版本始终为 64 位和 32 位版本使用不同的标签。
以下部分描述了可能注册的用户可见信息。对于 Python 3.5 及更早版本,这些信息均不可用,但为 PythonCore 键指定了替代默认值。
在其他公司名称下注册的环境没有向后兼容性要求,并且必须使用不同的标签来支持并行安装。使用这些注册的工具不需要区分标签,除非优先使用用户的设置。
公司
键的 Company 部分旨在对相关环境进行分组,并确保标签适当命名空间。键名应为字母数字,不带空格,并且可能唯一。例如,商标名称(首选)、主机名或作为最后手段,UUID 将是合适的
HKEY_CURRENT_USER\Software\Python\ExampleCorp
HKEY_CURRENT_USER\Software\Python\www.example.com
HKEY_CURRENT_USER\Software\Python\6C465E66-5A8C-4942-9E6A-D29159480C60
公司名称 PyLauncher 保留给 PEP 397 启动器(py.exe)。它不遵循此约定,应被工具忽略。
如果存在名为 DisplayName 的字符串值,则应使用它向用户识别环境制造商/开发人员/分销商。否则,应使用键名。(对于 PythonCore,默认显示名称是“Python Software Foundation”。)
如果存在名为 SupportUrl 的字符串值,则可以显示或以其他方式用于将用户引导至与环境相关的网站。(对于 PythonCore,默认支持 URL 是“https://pythonlang.cn/”。)
一个完整的示例如下所示
HKEY_CURRENT_USER\Software\Python\ExampleCorp
(Default) = (value not set)
DisplayName = "Example Corp"
SupportUrl = "http://www.example.com"
标签
键的 Tag 部分旨在在单个公司提供的环境中唯一标识一个环境。键名应为字母数字,不带空格,并在安装中保持稳定。例如,Python 语言版本、UUID 或部分/完整哈希将是合适的,而基于安装目录或当前机器的某些方面的 Tag 可能不合适。例如
HKEY_CURRENT_USER\Software\Python\ExampleCorp\examplepy
HKEY_CURRENT_USER\Software\Python\ExampleCorp\3.6
HKEY_CURRENT_USER\Software\Python\ExampleCorp\6C465E66
预计某些工具将要求用户在命令行中键入 Tag,并且只要 Tag 在所有 Python 安装中都是唯一的,Company 就可以是可选的。建议使用短小、易读且易于键入的 Tag,如果可能,请选择一个在所有其他 Company 中可能唯一的 Tag 值。
如果存在名为 DisplayName 的字符串值,则应使用它向用户识别环境。否则,应使用键名。(对于 PythonCore,默认值是“Python ”后跟 Tag。)
如果存在名为 SupportUrl 的字符串值,则可以显示或以其他方式用于将用户引导至与环境相关的网站。(对于 PythonCore,默认值是“https://pythonlang.cn/”。)
如果存在名为 Version 的字符串值,则应使用它来识别环境的版本。这与环境实现的 Python 版本无关。(对于 PythonCore,默认值是 Tag 的前三个字符。)
如果存在名为 SysVersion 的字符串值,则必须是 x.y 或 x.y.z 格式,与解释器中 sys.version_info 返回的版本匹配。如果省略,Python 版本未知。(对于 PythonCore,默认值是 Tag 的前三个字符。)
如果存在名为 SysArchitecture 的字符串值,则必须与 platform.architecture() 返回的元组的第一个元素匹配。通常,这将是“32bit”或“64bit”。如果省略,则架构未知。(对于 PythonCore,当注册在 HKEY_LOCAL_MACHINE\Software\Wow6432Node\Python 下或在 32 位操作系统上的任何位置时,架构为“32bit”,当注册在 64 位机器上 HKEY_LOCAL_MACHINE\Software\Python 下时,架构为“64bit”,当注册在 HKEY_CURRENT_USER 下时,架构未知。)
请注意,这些值都是推荐的,但不是强制的。省略 SysVersion 或 SysArchitecture 可能会阻止某些工具正确支持环境。一个完整的示例如下所示
HKEY_CURRENT_USER\Software\Python\ExampleCorp\examplepy
(Default) = (value not set)
DisplayName = "Example Py Distro 3"
SupportUrl = "http://www.example.com/distro-3"
Version = "3.0.12345.0"
SysVersion = "3.6.0"
SysArchitecture = "64bit"
安装路径
在环境键下,必须创建一个 InstallPath 键。此键始终命名为 InstallPath,默认值必须与 sys.prefix 匹配
HKEY_CURRENT_USER\Software\Python\ExampleCorp\3.6\InstallPath
(Default) = "C:\ExampleCorpPy36"
如果存在名为 ExecutablePath 的字符串值,则它必须是 python.exe(或等效)可执行文件的完整路径。如果省略,则环境不可执行。(对于 PythonCore,默认值是 (Default) 值引用的目录中的 python.exe 文件。)
如果存在名为 ExecutableArguments 的字符串值,则工具应在执行 ExecutablePath 时将该值用作第一个参数。工具可以在这些参数之后添加其他参数,并合理地期望标准的 Python 命令行选项可用。
如果存在名为 WindowedExecutablePath 的字符串值,则它必须是 pythonw.exe(或等效)可执行文件的路径。如果省略,则默认值为 ExecutablePath 的值,如果该值也省略,则环境不可执行。(对于 PythonCore,默认值是 (Default) 值引用的目录中的 pythonw.exe 文件。)
如果存在名为 WindowedExecutableArguments 的字符串值,则工具应在执行 WindowedExecutablePath 时将该值用作第一个参数。工具可以在这些参数之后添加其他参数,并合理地期望标准的 Python 命令行选项可用。
一个完整的示例如下所示
HKEY_CURRENT_USER\Software\Python\ExampleCorp\examplepy\InstallPath
(Default) = "C:\ExampleDistro30"
ExecutablePath = "C:\ExampleDistro30\ex_python.exe"
ExecutableArguments = "--arg1"
WindowedExecutablePath = "C:\ExampleDistro30\ex_pythonw.exe"
WindowedExecutableArguments = "--arg1"
帮助
在环境键下,可以创建一个 Help 键。如果存在,此键始终命名为 Help,并且没有默认值。
Help 的每个子键都指定了一个与环境关联的文档文件、工具或 URL。子键可以有任何名称,默认值是适合传递给 os.startfile 或等效函数的字符串。
如果存在名为 DisplayName 的字符串值,则应使用它向用户识别帮助文件。否则,应使用键名。
一个完整的示例如下所示
HKEY_CURRENT_USER\Software\Python\ExampleCorp\6C465E66\Help
Python\
(Default) = "C:\ExampleDistro30\python36.chm"
DisplayName = "Python Documentation"
Extras\
(Default) = "http://www.example.com/tutorial"
DisplayName = "Example Distro Online Tutorial"
其他键
Company-Tag 对下的所有其他子键都可供私人使用。
官方 CPython 版本传统上使用此空间中的某些键来确定 Python 标准库和其他已安装模块的位置。此行为主要出于向后兼容性而保留。但是,由于读取这些值的代码嵌入在解释器中,如果使用未修改的解释器,第三方发行版可能会受到写入 PythonCore 中的值的影响。
示例代码
此示例代码枚举注册表并显示可用于启动环境和目标可执行文件的可用 Company-Tag 对。它只显示标签最首选的目标。省略了 PythonCore 的向后兼容处理,但在后面的示例中有所显示
# Display most-preferred environments.
# Assumes a 64-bit operating system
# Does not correctly handle PythonCore compatibility
import winreg
def enum_keys(key):
i = 0
while True:
try:
yield winreg.EnumKey(key, i)
except OSError:
break
i += 1
def get_value(key, value_name):
try:
return winreg.QueryValue(key, value_name)
except FileNotFoundError:
return None
seen = set()
for hive, key, flags in [
(winreg.HKEY_CURRENT_USER, r'Software\Python', 0),
(winreg.HKEY_LOCAL_MACHINE, r'Software\Python', winreg.KEY_WOW64_64KEY),
(winreg.HKEY_LOCAL_MACHINE, r'Software\Python', winreg.KEY_WOW64_32KEY),
]:
with winreg.OpenKeyEx(hive, key, access=winreg.KEY_READ | flags) as root_key:
for company in enum_keys(root_key):
if company == 'PyLauncher':
continue
with winreg.OpenKey(root_key, company) as company_key:
for tag in enum_keys(company_key):
if (company, tag) in seen:
if company == 'PythonCore':
# TODO: Backwards compatibility handling
pass
continue
seen.add((company, tag))
try:
with winreg.OpenKey(company_key, tag + r'\InstallPath') as ip_key:
exec_path = get_value(ip_key, 'ExecutablePath')
exec_args = get_value(ip_key, 'ExecutableArguments')
if company == 'PythonCore' and not exec_path:
# TODO: Backwards compatibility handling
pass
except OSError:
exec_path, exec_args = None, None
if exec_path:
print('{}\\{} - {} {}'.format(company, tag, exec_path, exec_args or ''))
else:
print('{}\\{} - (not executable)'.format(company, tag))
此示例仅扫描当前用户的 PythonCore 条目。如果数据缺失,则替换为 PEP 中前面描述的默认值。请注意,这些默认值仅用于 PythonCore;其他注册没有任何默认值
# Only lists per-user PythonCore registrations
# Uses fallback values as described in PEP 514
import os
import winreg
def enum_keys(key):
i = 0
while True:
try:
yield winreg.EnumKey(key, i)
except OSError:
break
i += 1
def get_value(key, value_name):
try:
return winreg.QueryValue(key, value_name)
except FileNotFoundError:
return None
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Python\PythonCore") as company_key:
print('Company:', get_value(company_key, 'DisplayName') or 'Python Software Foundation')
print('Support:', get_value(company_key, 'SupportUrl') or 'https://pythonlang.cn/')
print()
for tag in enum_keys(company_key):
with winreg.OpenKey(company_key, tag) as tag_key:
print('PythonCore\\' + tag)
print('Name:', get_value(tag_key, 'DisplayName') or ('Python ' + tag))
print('Support:', get_value(tag_key, 'SupportUrl') or 'https://pythonlang.cn/')
print('Version:', get_value(tag_key, 'Version') or tag[:3])
print('SysVersion:', get_value(tag_key, 'SysVersion') or tag[:3])
# Architecture is unknown because we are in HKCU
# Tools may use alternate approaches to determine architecture when
# the registration does not specify it.
print('SysArchitecture:', get_value(tag_key, 'SysArchitecture') or '(unknown)')
try:
ip_key = winreg.OpenKey(company_key, tag + '\\InstallPath')
except FileNotFoundError:
pass
else:
with ip_key:
ip = get_value(ip_key, None)
exe = get_value(ip_key, 'ExecutablePath') or os.path.join(ip, 'python.exe')
exew = get_value(ip_key, 'WindowedExecutablePath') or os.path.join(ip, 'python.exe')
print('InstallPath:', ip)
print('ExecutablePath:', exe)
print('WindowedExecutablePath:', exew)
print()
此示例显示了 64 位 Python 3.6.0 的“仅为我安装”将创建的注册子集。也可能创建其他键
HKEY_CURRENT_USER\Software\Python\PythonCore
(Default) = (value not set)
DisplayName = "Python Software Foundation"
SupportUrl = "https://pythonlang.cn/"
HKEY_CURRENT_USER\Software\Python\PythonCore\3.6
(Default) = (value not set)
DisplayName = "Python 3.6 (64-bit)"
SupportUrl = "https://pythonlang.cn/"
Version = "3.6.0"
SysVersion = "3.6"
SysArchitecture = "64bit"
HKEY_CURRENT_USER\Software\Python\PythonCore\3.6\Help\Main Python Documentation
(Default) = "C:\Users\Me\AppData\Local\Programs\Python\Python36\Doc\python360.chm"
DisplayName = "Python 3.6.0 Documentation"
HKEY_CURRENT_USER\Software\Python\PythonCore\3.6\InstallPath
(Default) = "C:\Users\Me\AppData\Local\Programs\Python\Python36\"
ExecutablePath = "C:\Users\Me\AppData\Local\Programs\Python\Python36\python.exe"
WindowedExecutablePath = "C:\Users\Me\AppData\Local\Programs\Python\Python36\pythonw.exe"
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0514.rst