2016-04-19 21 views
10

Ich bin dabei, einige import hooks durch sys.meta_path, in einem etwas ähnlichen Ansatz zu this SO question. Dazu muss ich zwei Funktionen definieren find_module und load_module wie im obigen Link erläutert. Hier ist meine load_module Funktion,Import-Hooks für 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 

die für die meisten Module funktioniert gut, aber nicht für PyQt4.QtCore bei der Verwendung von Python 2.7:

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

mod = load_module(name, path) 

, die zurückkommt,

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 

Der gleiche Code funktioniert gut mit Python 3.4 (obwohl imp wird veraltet und importlib sollte idealerweise stattdessen dort verwendet werden).

Ich nehme an, das hat etwas mit der Initialisierung des dynamischen SIP-Moduls zu tun. Gibt es noch etwas, was ich mit Python 2.7 versuchen sollte?

Hinweis: Dies gilt sowohl für PyQt4 als auch für PyQt5.

bearbeiten: dies kann zu this question wie in der Tat in Beziehung gesetzt werden,

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

mit dem gleichen Fehler fehlschlägt. Noch bin ich nicht sicher, was für einen Weg, um es wäre ...

EDIT2: Nach @Nikita ‚s Anfrage zu einem Anwendungsfall beispielsweise Beton, was ich zu tun versuchen, um den Import umleiten Wenn also import A passiert, ist import B. Man könnte tatsächlich denken, dass es dafür ausreichen würde, das Modul in find_spec/find_module umzubenennen und dann den Standard load_module zu verwenden. Es ist jedoch unklar, wo eine Standard-load_module Implementierung in Python 2 zu finden ist. Die nächste Implementierung, die ich von etwas Ähnlichem gefunden habe, ist future.standard_library.RenameImport. Es sieht nicht so aus, als gäbe es einen Backport der vollständigen Implementierung von importlib von Python 3 auf 2.

Ein minimales Arbeitsbeispiel für die Import-Hooks, die dieses Problem reproduzieren, finden Sie in diesem gist.

+0

Wenn es sinnvoll sein kann, für einigen allgemeinen Kontext zu geben, was ich versuche zu tun, finden Sie in das [SiQt] (https://github.com/rth/SiQt) Paket, und dieses Problem ist diskutiert in [dieser GitHub-Ausgabe] (https://github.com/rth/SiQt/issues/4). – rth

+0

Ich verstehe Ihr Problem wirklich nicht, aber was ist falsch mit '__import __ ('PyQt4.QtCore')'. führt es zu unendlicher Rekursion? – danidee

+0

@danidee Mit "__import __ ('A')" ist nichts falsch, aber es ist äquivalent zur Verwendung von 'import A'. Was ich will, ist zu ändern, was passiert, wenn Sie das tun, und insbesondere "Import B" ausführen, wenn Sie "A" importieren. Dies kann mit Import-Hooks in 'sys.meta_path' geschehen, aber sie erfordern Funktionen auf niedrigerer Ebene wie' imp.load_module'. – rth

Antwort

4

UPD: Dieser Teil ist nicht wirklich relevant nach Antworten Updates, so siehe UPD unten.

Warum nicht einfach importlib.import_module verwenden, die sowohl in Python 2.7 und Python zur Verfügung 3:

#test.py 

import importlib 

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

auf Ubuntu 14.04:

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

Da es sich um ein dynamisches Modul ist, wie gesagt in der Fehler (und die eigentliche Datei ist QtCore.so), kann auch einen Blick auf imp.load_dynamic werfen.

Eine andere Lösung könnte sein, die Ausführung des Moduls Initialisierungscode zu erzwingen, aber IMO ist es zu viel Aufwand, also warum nicht einfach importlib verwenden.

UPD: Es gibt Dinge in pkgutil, die helfen könnten. Was ich in meinem Kommentar spricht, versuchen Sie Ihr Finder so zu ändern:

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 

Kann den Code oben jetzt nicht testen, so dass es bitte versuchen Sie es selbst.

P.S. Beachten Sie, dass imp.load_module(), die für Sie in Python 3 funktionierte, deprecated since Python 3.3 ist.

Eine andere Lösung ist nicht Haken überhaupt zu benutzen, sondern wickeln die __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__ 

Ausgang:

<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 zu pickle umbenannt und pickle ist standardmäßig Maschinen mit der importierten Variablenname shelve.

+0

Ich stimme deinen zwei ersten Ideen zu, leider funktionieren sie nicht, ich habe es schon mal ausprobiert. a) Soweit ich weiß, ist 'importlib.import_module 'zu hoch, um sie in' sys.meta_path'-Import-Hooks zu setzen. Was passiert, wenn Sie ein Paket importieren, wird es in 'sys.meta_path' aussehen, und wenn die' load_module' Funktion 'importlib.import_module' verwendet, wird es wieder in' sys.meta_path' suchen, wo es dasselbe 'load_module' finden wird Funktion usw., so erhalten Sie eine unendliche Rekursion Problem ... Was benötigt wird, ist etwas von niedrigeren Hebel wie 'imp.find_module' oder' importlib.machinery.SourceFileLoader – rth

+0

b) Ich habe 'imp versucht.load_dynamic', es erzeugt das gleiche Ergebnis (da es von 'imp.load_module' aufgerufen werden muss). c) Ja, ich weiß, dass ich dieses Modul lieber nicht manuell initialisieren möchte. Was ich nicht verstehe, ist warum ich muss (d. H. Welche Operation 'importlib.import_module' tut und' imp.load_module' nicht, die dies notwendig machen). Das Gleiche gilt für alle PyQt4/PyQt4-Submodule. Was ich versuche zu erreichen, ist 'SiQt.QtCore' zu ​​importieren, wenn' PyQt4.QtCore' importiert wird. Ich weiß, dass dies möglich ist, da Python future.standard_library.RenameImport es in PY2 macht (im Wesentlichen ist es nur Import umbenennen). – rth

+1

@rth, durch den Link, den Sie über Import-Hooks angegeben haben, heißt es, dass der Meta-Pfad-Finder 'find_spec' /' find_module' rekursiv für jeden Teil des Pfades aufrufen wird. Z.B. 'mpf.find_spec (" PyQt4 ", None, None)' und dann noch ein 'mpf.find_spec (" PyQt4.QtCore ", PyQt4 .__ path__, None)'. Wenn Sie also anstelle von 'find_spec' oder in einem anderen Teil von mpf hooken, können Sie' PyQt4' durch 'SiQt' im Namenstring ersetzen und dann die Standardmaschinerie aufrufen, um' SiQt' selbst laden zu lassen. Wenn ich falsch liege, stellen Sie bitte einen Code für Hooks zur Verfügung, um besser zu verstehen, was Sie erreichen möchten. – Nikita

3

Bei der Suche nach einem Modul, das Teil des Pakets wie PyQt4.QtCore ist, müssen Sie rekursiv jeden Teil des Namens ohne . finden. Und imp.load_module erfordert seinen name Parameter voller Modulname mit . Trennen von Paket und Modulname.

Da QtCore Teil eines Pakets ist, sollten Sie stattdessen python -c 'import PyQt4.QtCore' tun. Hier ist der Code zum Laden eines Moduls.

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) 

Test;

print(load_module('PyQt4.QtCore').qVersion()) 
4.8.6