10.12 导入模�的�时修改模�¶

问题¶

你想给�个已存在模�中的函数添加装饰器。 �过,��是这个模�已�被导入并且被使用过。

解决方案¶

这里问题的本质就是你想在模�被加载时执行�个动作。 �能是你想在一个模�被加载时触��个回调函数�通知你。

这个问题�以使用10.11�节中�样的导入钩�机制�实现。下�是一个�能的方案:

# postimport.py
import importlib
import sys
from collections import defaultdict

_post_import_hooks = defaultdict(list)

class PostImportFinder:
    def __init__(self):
        self._skip = set()

    def find_module(self, fullname, path=None):
        if fullname in self._skip:
            return None
        self._skip.add(fullname)
        return PostImportLoader(self)

class PostImportLoader:
    def __init__(self, finder):
        self._finder = finder

    def load_module(self, fullname):
        importlib.import_module(fullname)
        module = sys.modules[fullname]
        for func in _post_import_hooks[fullname]:
            func(module)
        self._finder._skip.remove(fullname)
        return module

def when_imported(fullname):
    def decorate(func):
        if fullname in sys.modules:
            func(sys.modules[fullname])
        else:
            _post_import_hooks[fullname].append(func)
        return func
    return decorate

sys.meta_path.insert(0, PostImportFinder())

这样,你就�以使用 when_imported() 装饰器了,例如:

>>> from postimport import when_imported
>>> @when_imported('threading')
... def warn_threads(mod):
...     print('Threads? Are you crazy?')
...
>>>
>>> import threading
Threads? Are you crazy?
>>>

作为一个更实际的例�,你�能想在已存在的定义上�添加装饰器,如下所示:

from functools import wraps
from postimport import when_imported

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Calling', func.__name__, args, kwargs)
        return func(*args, **kwargs)
    return wrapper

# Example
@when_imported('math')
def add_logging(mod):
    mod.cos = logged(mod.cos)
    mod.sin = logged(mod.sin)

讨论¶

本节技术�赖于10.11�节中讲述过的导入钩�,并�作修改。

@when_imported 装饰器的作用是注册在导入时被激活的处�器函数。 该装饰器检查sys.modules�查看模�是�真的已�被加载了。 如果是的�,该处�器被立�调用。�然,处�器被添加到 _post_import_hooks 字典中的一个列表中去。 _post_import_hooks 的作用就是收集所有的为�个模�注册的处�器对象。 一个模��以注册多个处�器。

�让模�导入�触�添加的动作,PostImportFinder 类被设置为sys.meta_path第一个元素。 它会�获所有模�导入�作。

本节中的 PostImportFinder 的作用并�是加载模�,而是自带导入完��触�相应的动作。 实际的导入被委派给�于sys.meta_path中的其他查找器。 PostImportLoader 类中的 imp.import_module() 函数被递归的调用。 为了��陷入无线循环,PostImportFinder ��了一个所有被加载过的模�集�。 如果一个模��存在就会直接被忽略掉。

当一个模�被 imp.import_module() 加载�, 所有在_post_import_hooks被注册的处�器被调用,使用新加载模�作为一个�数。

有一点需�注�的是本机�适用于那些通过 imp.reload() 被显�加载的模�。 也就是说,如果你加载一个之�已被加载过的模�,那么导入处�器将�会�被触�。 �外,�是你从sys.modules中删除模�然���新导入,处�器�会�一次触�。

更多关于导入�钩�信�请�考 PEP 369.