PEP 369 – 导入后钩子
- 作者:
- Christian Heimes <christian at python.org>
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 创建:
- 2008 年 1 月 2 日
- Python 版本:
- 2.6, 3.0
- 历史记录:
- 2012 年 12 月 2 日
撤回通知
本 PEP 已由其作者撤回,因为在 Python 3.3 迁移到 importlib 后,许多详细的设计不再有效。
摘要
本 PEP 提出对导入机制的增强,以添加导入后钩子。其主要目的是支持在 Python 3.0 中预期的更广泛的抽象基类使用。
该 PEP 最初是关于延迟导入和导入后钩子的合并 PEP。在 python-dev 邮件列表中进行了一些讨论后,PEP 被分成两个单独的 PEP。 [1]
理由
Python 没有 API 可以钩入导入机制并在模块成功加载后执行代码。 PEP 302 的导入钩子是关于查找模块和加载模块,但它们不是为了作为导入后钩子而设计的。
用例
Alyssa (Nick) Coghlan 在其关于模块导入回调的初始帖子 [2] 中提到了导入后钩子的用例。它是在 Python 3.0 及其 ABC 的开发过程中发现的。我们想将诸如 decimal.Decimal 之类的类注册到 ABC,但该模块不应在每次解释器启动时都导入。Alyssa 想出了这个例子
@imp.when_imported('decimal')
def register(decimal):
Inexact.register(decimal.Decimal)
函数 register
注册为名为 'decimal' 的模块的回调。当导入 decimal 时,将使用模块对象作为参数调用该函数。
虽然这个特定的例子在实践中并不需要(因为 decimal.Decimal 将在 2.6 和 3.0 中从适当的抽象 Number 基类继承),但它仍然说明了这个原理。
现有实现
PJE 的 peak.util.imports [3] 实现加载后钩子。我的实现与他的有很多共同之处,部分基于他的想法。
导入后钩子实现
导入后钩子在模块加载后被调用。钩子是可调用对象,它们接受一个参数,即模块实例。它们通过模块的点分隔名称注册,例如“os”或“os.path”。
可调用对象存储在字典 sys.post_import_hooks
中,该字典是将名称(作为字符串)映射到可调用对象列表或 None 的映射。
状态
没有注册钩子
sys.post_import_hooks 不包含该模块的条目
已注册钩子,但模块尚未加载
导入钩子注册表包含一个条目 sys.post_import_hooks[“name”] = [hook1]
模块已成功加载
导入机制检查 sys.post_import_hooks 是否包含为新加载的模块注册的导入后钩子。如果找到钩子,则按注册顺序调用它们,并将模块实例作为第一个参数。当一个方法引发异常时,钩子的处理将停止。最后,即使发生错误,模块名称的条目也会设置为 None。
此外,模块对象的新的 __notified__
插槽设置为 True
,以防止当通知方法在钩子内部调用时出现无限递归。对于不从 PyModule
子类的对象,将添加一个新的属性。
无法加载模块
导入钩子既不会被调用,也不会从注册表中删除。以后可能可以加载该模块。
已注册钩子,但模块已加载
钩子立即触发。
不变式
导入钩子系统保证某些不变式。XXX
Python 示例实现
Python 实现可能看起来像
def notify(name):
try:
module = sys.modules[name]
except KeyError:
raise ImportError("Module %s has not been imported" % (name,))
if module.__notified__:
return
try:
module.__notified__ = True
if '.' in name:
notify(name[:name.rfind('.')])
for callback in post_import_hooks[name]:
callback(module)
finally:
post_import_hooks[name] = None
XXX
C API
新的 C API 函数
PyObject* PyImport_GetPostImportHooks(void)
- 返回字典 sys.post_import_hooks 或 NULL
PyObject* PyImport_NotifyLoadedByModule(PyObject *module)
- 通知导入后系统已请求模块。返回对同一模块对象的借用引用,如果发生错误则返回 NULL。该函数只调用该模块本身的钩子,而不调用其父模块的钩子。该函数必须在获取导入锁的情况下调用。
PyObject* PyImport_NotifyLoadedByName(const char *name)
PyImport_NotifyLoadedByName("a.b.c")
按特定顺序分别调用PyImport_NotifyLoadedByModule()
的a
、a.b
和a.b.c
。这些模块从sys.modules
中检索。如果无法检索模块,则会引发异常,否则将返回对modname
的借用引用。钩子调用始终从主父模块开始。PyImport_NotifyLoadedByName() 的调用者必须持有导入锁!PyObject* PyImport_RegisterPostImportHook(PyObject *callable, PyObject *mod_name)
- 为模块
mod_name
注册新的钩子callable
int PyModule_GetNotified(PyObject *module)
- 返回
__notified__
插槽/属性的状态。 int PyModule_SetNotified(PyObject *module, int status)
- 设置
__notified__
插槽/属性的状态。
PyImport_NotifyLoadedByModule()
方法在 import_submodule()
内部调用。导入系统确保获取导入锁,并且父模块的钩子已调用。
Python API
导入钩子注册表和两个新的 API 方法通过 sys
和 imp
模块公开。
sys.post_import_hooks
- 该字典包含导入后钩子
{"name" : [hook1, hook2], ...}
imp.register_post_import_hook(hook: "callable", name: str)
- 为模块 name 注册新的钩子 hook
imp.notify_module_loaded(module: "module instance") -> module
- 通知系统模块已加载。该方法提供用于与现有延迟/延期导入扩展的兼容性。
module.__notified__
- 模块实例的插槽。XXX
when_imported 函数装饰器也在 imp 模块中,它等效于
def when_imported(name):
def register(hook):
register_post_import_hook(hook, name)
return register
- imp.when_imported(name) -> 装饰器函数
- 对于 @when_imported(name) def hook(module): pass
待解决的问题
when_imported 装饰器尚未编写。
该代码包含几个 XXX 注释。它们主要与边缘情况下的错误处理有关。
向后兼容性
新功能和 API 不与 Python 的旧导入系统冲突,并且不会对大多数软件造成任何向后兼容性问题。但是,诸如 PEAK 和 Zope 之类的实现自己延迟导入魔术的系统需要遵循一些规则。
导入后钩子经过精心设计,可以与现有的延期和延迟导入系统协同工作。PEP 作者建议用新的钩子 API 替换自己的加载时钩子。可选的延迟或延期导入仍将起作用,但实现必须调用 imp.notify_module_loaded
函数。
参考实现
参考实现已经编写,可以在 py3k-importhook 分支中获得。 [4] 它仍然需要一些清理、文档更新和额外的单元测试。
致谢
Alyssa Coghlan,感谢她的校对和最初的讨论。Phillip J. Eby,感谢他在 PEAK 中的实现和对我的实现的帮助。
版权
本文档已置于公有领域。
参考资料
来源: https://github.com/python/peps/blob/main/peps/pep-0369.rst
最后修改: 2023-10-11 12:05:51 GMT