2016-04-19 6 views
10

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

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.

Редактировать: это может быть связано с this question как, впрочем,

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

терпит неудачу с такой же ошибкой. Тем не менее я не уверен, что будет путь вокруг него ...

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

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

+0

Если это может быть полезно, чтобы дать некоторые общий контекст для того, что я пытаюсь сделать, см пакета [SiQt] (https://github.com/rth/SiQt), и эта проблема обсуждался в [этой проблеме github] (https://github.com/rth/SiQt/issues/4). – rth

+0

Я действительно не понимаю вашу проблему, но что не так с '__import __ ('PyQt4.QtCore')'. приводит ли она к бесконечной рекурсии? – danidee

+0

@ danidee Ничто не работает с '__import __ ('A')', но это эквивалентно использованию 'import A'. Я хочу изменить то, что происходит, когда вы это делаете, и, в частности, запустить 'import B', когда вы импортируете A'. Это можно сделать с помощью захватов импорта в 'sys.meta_path', но для них требуются более низкие функции уровня, такие как' imp.load_module'. – rth

ответ

4

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, - deprecated since 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.

+0

Я согласен с двумя твоими идеями, к сожалению, они не работают, я пробовал это раньше. a) Насколько я понимаю, '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 – rth

+0

b) Я попробовал '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 (по сути, это просто импортное переименование). – rth

+1

@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'. Если я ошибаюсь, предоставьте код, используемый для перехвата, чтобы лучше понять, чего вы пытаетесь выполнить. – Nikita

3

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

Потому что QtCore является частью пакета, вы shoud do 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 
Смежные вопросы