Правильный способ реализации хуков после импорта в python-3.4+

Я хочу изменить поведение импорта. Я нашел пример с обработчиком post import в книге Дэвида Бизли и Брайана К. Джонса «Поваренная книга Python», который должен удовлетворить мою проблему.

Поскольку последняя редакция была опубликована во времена python 3.3, предоставленный пример устарел, мне пришлось изменить его самостоятельно. В исходной версии в PostImportFinder класс find_module(self, fullname, path = None) определяется и в PostImportLoader вместо того create_module() load_module()был определен. Вот небольшой воспроизводимый пример:

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

_post_import_hooks = defaultdict(list)

class PostImportFinder:
    def __init__(self):
        self._skip=set()
    
    def find_spec(self, fullname, path = None, target = 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 create_module(self, spec):
        importlib.import_module(spec.name)
        module = sys.modules[spec.name]
        for func in _post_import_hooks[spec.name]:
            func(module)
        self._finder._skip.remove(spec.name)
        return module
    
def when_imported(names):
    def decorate(func):
        for fullname in names:
            if fullname in sys.modules:
                print(f'importing {fullname}')
                func(sys.modules[fullname])
            else:
                _post_import_hooks[fullname].append(func)
        return func
    return decorate

sys.meta_path.insert(0,PostImportFinder)
 
      #postimportfunc.py
from inspect import getmembers, isfunction, isclass
from postimport import when_imported


list_of_module_names = ['simple']
#Some decorator. For example purposes pretty simple
@when_imported(list_of_module_names)
def decorate(mod):
    # Decorate classes
    print(f'module {mod} imported')

      #simple.py
class A:
    def __init__(self):
        self.x=42
    
    def bar(self):
        print(self.x)
      #start.py
import postimportfunc
from simple import A


foo = A()
foo.bar()


Когда я запускаю start.py возникает следующая ошибка:

          runfile('/home/user/reproduce/start.py', wdir='/home/user/reproduce')

  File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 827, in runfile
    execfile(filename, namespace)

  File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 110, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

  File "/home/user/reproduce/start.py", line 2, in <module>
    import postimportfunc

  File "<frozen importlib._bootstrap>", line 991, in _find_and_load

  File "<frozen importlib._bootstrap>", line 971, in _find_and_load_unlocked

  File "<frozen importlib._bootstrap>", line 914, in _find_spec

  File "/home/user/reproduce/postimport.py", line 12, in find_spec
    if fullname in self._skip:

AttributeError: 'str' object has no attribute '_skip' 

Итак, мои вопросы очевидны: что я делаю не так? Я просто не понимаю, как и str связаны, так как я инициализирую _skipкак набор. Есть ли другой / лучший подход, как изменить поведение постимпорта для указанных модулей?

1 ответ

Основная причина - тупая опечатка в последней строке postimport.py. Вместо того:

      sys.meta_path.insert(0,PostImportFinder)

Должен быть:

      sys.meta_path.insert(0,PostImportFinder()) #DO NOT FORGET PARENTHESIS

Но это была не единственная проблема. Мне также пришлось изменить следующее:

в PostImportFinder переименовать find_spec(self, fullname, path = None, targer = None) в find_module(self, fullname, path = None) И в PostImportLoader заменять find_spec()с участием:

      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

Итак, резюмируя: я должен был придерживаться рецепта из книги, упомянутой в вопросе.

Другие вопросы по тегам