Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

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()aa.ba.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 方法通过 sysimp 模块公开。

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