Импорт хуков для PyQt4.QtCore

Я пытаюсь настроить некоторые перехватчики импорта через sys.meta_path, используя несколько похожий подход. на этот вопрос SO. Для этого мне нужно определить две функции find_module и load_module, как описано в ссылке выше. Вот моя функция load_module,

import imp

def load_module(name, path):
    fp, pathname, description = imp.find_module(name, path)

    try:
        module = imp.load_module(name, fp, pathname, description)
    finally:
        if fp:
             fp.close()
    return module

который отлично работает для большинства модулей, но не работает для PyQt4.QtCore при использовании Python 2.7:

name = "QtCore"
path = ['/usr/lib64/python2.7/site-packages/PyQt4']

mod = load_module(name, path)

который возвращается,

Traceback (most recent call last):
   File "test.py", line 19, in <module>
   mod = load_module(name, path)
   File "test.py", line 13, in load_module
   module = imp.load_module(name, fp, pathname, description)
SystemError: dynamic module not initialized properly

Тот же код отлично работает с Python 3.4 (хотя imp устаревает и importlib).

Я предполагаю, что это как-то связано с инициализацией динамического модуля SIP. Есть ли что-нибудь еще, что я должен попробовать с Python 2.7?

Примечание: это относится как к PyQt4, так и к PyQt5.

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

cd /usr/lib64/python2.7/site-packages/PyQt4
python2 -c 'import QtCore'

вылетает с той же ошибкой. Тем не менее, я не уверен, что можно было бы обойти это...

Edit2: следуя запросу @Nikita о конкретном примере использования, я пытаюсь перенаправить импорт, поэтому, когда кто-то делает import A, происходит следующее: import B. Можно было бы подумать, что для этого достаточно будет переименовать модуль в find_spec/find_module, а затем использовать дефолтный load_module. Однако неясно, где найти реализацию load_module по умолчанию в Python 2. Ближайшая реализация чего-то подобного, которую я нашел, — это future.standard_library.RenameImport. Не похоже, что есть бэкпорт полной реализации importlib с Python 3 на 2.

Минимальный рабочий пример перехватчика импорта, который воспроизводит эту проблему, можно найти в этом суть.


person rth    schedule 19.04.2016    source источник
comment
Если это может быть полезно, чтобы дать некоторый общий контекст того, что я пытаюсь сделать, см. SiQt, и эта проблема обсуждается в этой проблеме github.   -  person rth    schedule 21.04.2016
comment
я действительно не понимаю вашей проблемы, но что не так с __import__('PyQt4.QtCore'). приводит ли это к бесконечной рекурсии?   -  person danidee    schedule 22.04.2016
comment
@danidee В __import__('A') нет ничего плохого, но это эквивалентно использованию import A. Я хочу изменить то, что происходит, когда вы это делаете, и, в частности, запускаете import B, когда вы import A. Это можно сделать с помощью перехватчиков импорта в sys.meta_path, но для них требуются функции более низкого уровня, такие как imp.load_module.   -  person rth    schedule 24.04.2016
comment
@rth, действительно, в документах о imporylib в Python 2.7 написано: Этот модуль является второстепенным подмножеством того, что доступно в более полнофункциональном одноименном пакете из Python 3.1, обеспечивающем полную реализацию импорта. . Есть мысли о пользовательском импорте в PEP302, я посмотрю на это и поделиться своими мыслями в обновлении ответа.   -  person Nikita    schedule 25.04.2016


Ответы (2)


UPD: эта часть не очень актуальна после обновления ответов, поэтому см. UPD ниже.

Почему бы просто не использовать importlib.import_module, который доступен как в Python 2.7, так и в Python 3:

#test.py

import importlib

mod = importlib.import_module('PyQt4.QtCore')
print(mod.__file__)

в Ubuntu 14.04:

$ python2 test.py 
/usr/lib/python2.7/dist-packages/PyQt4/QtCore.so

Поскольку это динамический модуль, как указано в ошибке (и фактический файл QtCore.so), можно также взглянуть на imp.load_dynamic.

Другим решением может быть принудительное выполнение кода инициализации модуля, но IMO это слишком хлопотно, так почему бы просто не использовать importlib.

UPD: в pkgutil есть вещи, которые могут помочь. То, о чем я говорил в своем комментарии, попробуйте изменить свой искатель следующим образом:

import pkgutil

class RenameImportFinder(object):

    def find_module(self, fullname, path=None):
        """ This is the finder function that renames all imports like
             PyQt4.module or PySide.module into PyQt4.module """
        for backend_name in valid_backends:
            if fullname.startswith(backend_name):
                # just rename the import (That's what i thought about)
                name_new = fullname.replace(backend_name, redirect_to_backend)
                print('Renaming import:', fullname, '->', name_new, )
                print('   Path:', path)


                # (And here, don't create a custom loader, get one from the
                # system, either by using 'pkgutil.get_loader' as suggested
                # in PEP302, or instantiate 'pkgutil.ImpLoader').

                return pkgutil.get_loader(name_new) 

                #(Original return statement, probably 'pkgutil.ImpLoader'
                #instantiation should be inside 'RenameImportLoader' after
                #'find_module()' call.)
                #return RenameImportLoader(name_orig=fullname, path=path,
                #       name_new=name_new)

    return None

Сейчас нет возможности протестировать приведенный выше код, поэтому попробуйте сами.

P.S. Обратите внимание, что imp.load_module(), который работал у вас в Python 3, устарел, начиная с Python 3.3< /а>.

Другое решение — вообще не использовать хуки, а вместо этого обернуть __import__:

print(__import__)

valid_backends = ['shelve']
redirect_to_backend = 'pickle'

# Using closure with parameters 
def import_wrapper(valid_backends, redirect_to_backend):
    def wrapper(import_orig):
        def import_mod(*args, **kwargs):
            fullname = args[0]
            for backend_name in valid_backends:
                if fullname.startswith(backend_name):
                    fullname = fullname.replace(backend_name, redirect_to_backend)
                    args = (fullname,) + args[1:]
            return import_orig(*args, **kwargs)
        return import_mod
    return wrapper

# Here it's important to assign to __import__ in __builtin__ and not
# local __import__, or it won't affect the import statement.
import __builtin__
__builtin__.__import__ = import_wrapper(valid_backends, 
                                        redirect_to_backend)(__builtin__.__import__)

print(__import__)

import shutil
import shelve
import re
import glob

print shutil.__file__
print shelve.__file__
print re.__file__
print glob.__file__

выход:

<built-in function __import__>
<function import_mod at 0x02BBCAF0>
C:\Python27\lib\shutil.pyc
C:\Python27\lib\pickle.pyc
C:\Python27\lib\re.pyc
C:\Python27\lib\glob.pyc

shelve переименовывается в pickle, а pickle импортируется по умолчанию с именем переменной shelve.

person Nikita    schedule 22.04.2016
comment
Я согласен с двумя вашими первыми идеями, к сожалению, они не работают, я пробовал это раньше. а) Насколько я понимаю, importlib.import_module слишком высокий уровень, чтобы вставлять sys.meta_path импортные хуки. Что происходит, когда вы импортируете пакет, он будет искать в sys.meta_path, и если функция load_module использует importlib.import_module, она снова будет искать в sys.meta_path, где найдет ту же функцию load_module и т. д., так что вы получите проблему бесконечной рекурсии... Что нужно это что-то более низкого уровня, такое как imp.find_module или `importlib.machinery.SourceFileLoader - person rth; 22.04.2016
comment
б) Я пробовал imp.load_dynamic, он дает тот же результат (поскольку он должен быть вызван imp.load_module, я полагаю). c) Да, я знаю, что не хотел бы инициализировать этот модуль вручную. Чего я не понимаю, так это почему я должен (т.е. какая операция importlib.import_module делает, а imp.load_module нет, что делает это необходимым). То же самое верно для всех подмодулей PyQt4/PyQt4. Я пытаюсь добиться импорта SiQt.QtCore при импорте PyQt4.QtCore. Я знаю, что это возможно, поскольку python future.standard_library.RenameImport делает это в PY2 (по сути, это просто переименование импорта). - person rth; 22.04.2016
comment
@rth, по предоставленной вами ссылке о перехватчиках импорта говорится, что средство поиска метапутей будет рекурсивно вызывать find_spec/find_module для каждой части пути. Например. mpf.find_spec("PyQt4", None, None) и еще один mpf.find_spec("PyQt4.QtCore", PyQt4.__path__, None). Поэтому, если вы подключаетесь вместо find_spec или в какой-либо другой части mpf, можно заменить PyQt4 на SiQt в строке имени, а затем вызвать механизм по умолчанию, чтобы он мог загрузить SiQt сам по себе. Если я ошибаюсь, пожалуйста, предоставьте код, используемый для хуков, чтобы лучше понять, чего вы пытаетесь достичь. - person Nikita; 22.04.2016
comment
Я согласен с тем, что использование механизма по умолчанию для load_module было бы неплохо. См. Edit2 в вопросе выше. - person rth; 25.04.2016
comment
@rth, если хуки не сработают, вы можете обернуть __import__, проверьте обновление ответа. - person Nikita; 25.04.2016

При поиске модуля, который является частью пакета, такого как PyQt4.QtCore, вы должны рекурсивно найти каждую часть имени без .. И imp.load_module требует, чтобы его параметр name был полным именем модуля с ., разделяющим пакет и имя модуля.

Поскольку QtCore является частью пакета, вместо него следует использовать python -c 'import PyQt4.QtCore'. Вот код для загрузки модуля.

import imp

def load_module(name):
    def _load_module(name, pkg=None, path=None):
        rest = None
        if '.' in name:
            name, rest = name.split('.', 1)
        find = imp.find_module(name, path)
        if pkg is not None:
            name = '{}.{}'.format(pkg, name)
        try:
            mod = imp.load_module(name, *find)
        finally:
            if find[0]:
                find[0].close()
        if rest is None:
            return mod 
        return _load_module(rest, name, mod.__path__)
    return _load_module(name)

Тест;

print(load_module('PyQt4.QtCore').qVersion())  
4.8.6
person Nizam Mohamed    schedule 28.04.2016