2016-04-19 13 views
10

Sto tentando di impostare alcuni import hooks tramite sys.meta_path, con un approccio simile a this SO question. Per questo, ho bisogno di definire due funzioni find_module e load_module come spiegato nel link sopra. Qui è la mia funzione load_module,Ganci di importazione per 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 

che funziona bene per la maggior parte dei moduli, ma non riesce per PyQt4.QtCore quando si usa Python 2.7:

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

mod = load_module(name, path) 

che restituisce,

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 

Le stesse opere di codice bene con Python 3.4 (anche se imp sta diventando deprecato e idealmente dovrebbe essere usato importlib).

Suppongo che questo abbia qualcosa a che fare con l'inizializzazione del modulo dinamico SIP. C'è qualcos'altro che dovrei provare con Python 2.7?

Nota: questo vale sia per PyQt4 sia per PyQt5.

Modifica: questo potrebbe essere correlato a this question come del resto,

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

fallisce con lo stesso errore. Ancora non sono sicuro di quello che sarebbe un modo intorno ad esso ...

Edit2: in seguito alla richiesta @Nikita s' per un caso ad esempio uso concreto, quello che sto cercando di fare è di reindirizzare l'importazione , quindi quando uno fa import A, quello che succede è import B. Si potrebbe davvero pensare che per questo sarebbe sufficiente eseguire la ridenominazione del modulo in find_spec/find_module e quindi utilizzare il valore predefinito load_module. Tuttavia, non è chiaro dove trovare un'implementazione di default in load_module in Python 2. L'implementazione più vicina che ho trovato di qualcosa di simile è future.standard_library.RenameImport. Non sembra che ci sia un backport dell'implementazione completa di importlib da Python 3 a 2.

Un esempio di lavoro minimo per i ganci di importazione che riproduce questo problema è disponibile in questo gist.

+0

Se può essere utile, per dare un po 'di contesto generale per quello che sto cercando di fare, vedere il pacchetto [SiQt] (https://github.com/rth/SiQt), e questo problema è discusso in [questo problema github] (https://github.com/rth/SiQt/issues/4). – rth

+0

davvero non capisco il tuo problema, ma cosa c'è di sbagliato con '__import __ ('PyQt4.QtCore')'. porta alla ricorsione infinita? – danidee

+0

@danidee Non c'è niente di sbagliato in '__import __ ('A')', ma è equivalente all'utilizzo di 'import A'. Quello che voglio è cambiare cosa succede quando lo fai, e in particolare esegui "import B", quando tu "importi A". Questo può essere fatto con ganci di importazione in 'sys.meta_path', ma richiedono funzioni di livello inferiore come' imp.load_module'. – rth

risposta

4

UPD: Questa parte non è molto pertinente dopo gli aggiornamenti delle risposte, quindi vedere UPD di seguito.

Perché non usare importlib.import_module, che è disponibile sia in Python 2.7 e Python 3:

#test.py 

import importlib 

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

su Ubuntu 14.04:

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

Poiché è un modulo dinamico, come ha detto in l'errore (e il file attuale è QtCore.so), potrebbe anche dare un'occhiata a imp.load_dynamic.

Un'altra soluzione potrebbe essere quella di forzare l'esecuzione del codice di inizializzazione del modulo, ma IMO è troppo complicato, quindi perché non usare solo importlib.

UPD: Ci sono cose nello pkgutil, che potrebbero essere d'aiuto. Quello che mi parlavo nel mio commento, tentare di modificare il vostro finder come questo:

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 

Non può testare il codice qui sopra ora, quindi per favore provate voi stessi.

P.S. Nota che imp.load_module(), che ha funzionato per te in Python 3 è deprecated since Python 3.3.

Un'altra soluzione non è quella di utilizzare ganci a tutti, ma invece avvolgere il __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__ 

uscita:

<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 rinominato per pickle, e pickle viene importato dalle macchine default con il nome variabile shelve.

+0

Sono d'accordo con le tue prime due idee, purtroppo non funzionano, l'ho provato prima. a) Per quanto ho capito, 'importlib.import_module' è troppo alto per inserire un hook' sys.meta_path'. Quello che succede è quando importate un pacchetto che apparirà in 'sys.meta_path', e se la funzione' load_module' usa 'importlib.import_module' cercherà di nuovo in' sys.meta_path' dove troverà lo stesso 'load_module' funzione etc, in modo da ottenere un problema di ricorsione infinito ... Ciò che è necessario è qualcosa di lower lever come 'imp.find_module' o' importlib.machinery.SourceFileLoader – rth

+0

b) Ho provato 'imp.load_dynamic', produce lo stesso risultato (dal momento che deve essere chiamato da 'imp.load_module' suppongo). c) Sì, so che preferirei non inizializzare quel modulo a mano. Quello che non capisco è perché devo (cioè quale operazione 'importlib.import_module' fa e 'imp.load_module' no, che rendono necessario ciò). Lo stesso vale per tutti i sottomoduli PyQt4/PyQt4. Quello che sto cercando di ottenere è importare 'SiQt.QtCore' quando viene importato' PyQt4.QtCore'. So che questo è possibile dal momento che python future.standard_library.RenameImport lo fa in PY2 (essenzialmente è solo la rinomina dell'importazione). – rth

+1

@rth, dal link che hai fornito sui ganci di importazione, dice che il meta percorso finder chiamerà 'find_spec' /' find_module' in modo ricorsivo per ogni parte del percorso. Per esempio. 'mpf.find_spec (" PyQt4 ", None, None)' e poi un altro 'mpf.find_spec (" PyQt4.QtCore ", PyQt4 .__ path__, None)'. Quindi, se ti stai collegando al posto di 'find_spec' o in qualche altra parte di mpf, puoi sostituire' PyQt4' con 'SiQt' nella stringa del nome e poi chiamare il macchinario predefinito per lasciar caricare 'SiQt' da solo. Se ho torto, per favore, fornisci del codice usato per i ganci per capire meglio cosa stai cercando di realizzare. – Nikita

3

Quando si trova un modulo che fa parte di un pacchetto come PyQt4.QtCore, è necessario trovare in modo ricorsivo ogni parte del nome senza .. E imp.load_module richiede che il suo parametro name sia il nome completo del modulo con . che separa il pacchetto e il nome del modulo.

Perché QtCore fa parte di un pacchetto, è necessario invece fare python -c 'import PyQt4.QtCore'. Ecco il codice per caricare un modulo.

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