【问题标题】:Post import hooks in Python 3在 Python 3 中发布导入钩子
【发布时间】:2016-11-16 04:05:09
【问题描述】:

我希望在导入特定模块时运行一些回调。例如(使用一个实际上并不存在的假 @imp.when_imported 函数):

@imp.when_imported('numpy')
def set_linewidth(numpy):
    import shutil
    numpy.set_printoptions(linewidth=shutil.get_terminal_size()[0])

此功能是在PEP 369: Post import hooks 中设计的,但由于以下原因被撤回:

此 PEP 已被其作者撤回,因为在 Python 3.3 中迁移到 importlib 后,大部分详细设计不再有效。

但是importlib 没有明确的解决方案。如何使用importlib 实现导入后挂钩?

【问题讨论】:

  • 你能在模块的最后添加代码吗?
  • 不,我想为我不拥有的模块执行此操作(尤其是numpy)。
  • 必须在导入之后还是在主代码之前?
  • 如果您提供的模块中有on_import 钩子,您不妨导入 numpy 并立即运行该函数(因为模块在整个解释器中是全局的)。另一个考虑因素是,这个“功能”对您提供的模块的用户来说是隐含的和隐藏的,他们可能不希望这样做。
  • 没有on_import 钩子——这就是我要问的。而且我不想总是导入 numpy,因为它很慢或者可能不可用。

标签: python python-3.x python-import


【解决方案1】:

wrapt 模块提供了一个实现。

观看有关wrapt 的视频,包括此功能:

不要认为wrapt 的文档提到它。

一些博文在文末:

尽管谈论它。

wrapt 有一个名为 autowrapt 的配套模块,它允许您使用此机制进行猴子修补,而无需更改应用程序代码本身来触发它。

【讨论】:

  • 感谢您的提示!我想避免依赖第三方模块,所以我正在寻找一个使用 importlib 的简单解决方案,因为 PEP 369 暗示这很容易。不过看wrapt's implementation好像我的问题的答案没那么简单。
  • 我建议更新解决方案以链接到github.com/GrahamDumpleton/wrapt/blob/master/src/wrapt/…,这是 PEP 369 的完整实现。我想我可以复制文件并将其剥离为基本要素,但现在我会只需使用包装。谢谢!
【解决方案2】:

我会震惊地发现这是执行此操作的最佳方法......但是,从早期的 python2.x 版本开始,猴子修补 __import__ 就得到了支持。我们可以在这里利用它:

try:
    import builtins  # python3.x
except ImportError:
    import __builtin__ as builtins  # python2.x
import sys
import collections

_builtin_import = builtins.__import__

def _my_import(name, globals=None, locals=None, fromlist=(), level=0):
    already_imported = name in sys.modules

    mod = _builtin_import(
        name,
        globals=globals,
        locals=locals,
        fromlist=fromlist,
        level=level)

    if not already_imported and name in _post_import_hooks:
        for hook in _post_import_hooks[name]:
            hook()
    return mod

builtins.__import__ = _my_import

_post_import_hooks = collections.defaultdict(list)

def on_import(name):
    def decorator(func):
        _post_import_hooks[name].append(func)
        return func
    return decorator

@on_import('numpy')
def print_hi():
    print('Hello Numpy')

print('before numpy')
import numpy
print('after numpy')

这个答案使一个 super 简单的注册表用于注册回调。装饰器只是注册函数然后返回它。它不会做任何花哨的检查(例如,模块是否已经加载),但可以很容易地扩展来做到这一点。

显然,不利的一面是,如果某个其他模块决定对 __import__ 进行猴子补丁,那么您就不走运了——这个模块或另一个模块可能最终会损坏。

我已经对此进行了测试,它似乎适用于 python2.x 和 python3.x。

【讨论】:

  • 替换__import__ 几乎从它存在的那一刻起就被人们所反对。更改导入机制并引入sys.meta_path 的原因之一是阻止人们这样做。
  • @GrahamDumpleton -- 当然。我什至在我的答案中说明了它不好的至少一个原因(如果多个模块尝试替换它,通常只有一个会成功)。我还说,如果这是最好的方法,我会感到震惊。我仍在等待找出最好的方法。也许我需要花一些时间来弄清楚你上面建议的 wrapt 的源代码......
  • 好方法,它有效!!!!!!!!!!这是一个非常棒的解决方案,非常感谢~~~
【解决方案3】:

这行得通吗?

import importlib

class ImportHook:

    def __init__(self, func):
        self.func = func
        self.module = None

    def __enter__(self):
        return self

    def get_module(self, module_name):
        self.module = importlib.import_module(module_name)
        return self.module

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.module is not None:
            self.func(self.module)

def set_linewidth(module):
    import shutil
    module.set_printoptions(linewidth=shutil.get_terminal_size()[0])

with ImportHook(set_linewidth) as hook:
    numpy = hook.get_module('numpy')

【讨论】:

  • 没有。我不明白这将如何工作。 AFAICT,所有这一切都是创建一个numpy 变量。我很确定解决方案涉及设置sys.path_hookssys.meta_path
  • with as 块导入 numpy 并在完成时调用钩子。
  • @BallpointBen:但你不需要钩子。如果你要导入numpy,你可以在之后运行“hook”代码。目标是注册一个钩子以防有人稍后导入numpy,但根本不导入numpy,除非其他人导入它。并且导入numpy 的代码不知道你的钩子,并且无法更改以添加对它的支持。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-15
  • 2020-05-04
  • 1970-01-01
相关资源
最近更新 更多