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")以特定顺序为a、a.b和a.b.c调用PyImport_NotifyLoadedByModule()。模块从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
最后修改于: 2025-02-01 08:59:27 GMT