Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

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") 以特定顺序为 aa.ba.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 方法通过 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

最后修改于: 2025-02-01 08:59:27 GMT