2016-04-19 56 views
10

我正在尝试设置一些import hookssys.meta_path,与this SO question有点类似。为此,我需要定义两个函数find_moduleload_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?

注意:这适用于PyQt4PyQt5

编辑:这可能与this question因为实际上,

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

失败,出现同样的错误。不过我不知道会是什么办法解决它......

EDIT2:以下为具体使用情况实例@Nikita的要求,我所要做的是重新进口,所以当一个人做import A,会发生什么import B。我们的确可以认为,对于此模块,只需在find_spec/find_module中进行模块重命名即可,然后使用默认的load_module即可。然而,目前还不清楚在Python 2中如何找到默认的load_module实现。我发​​现的类似的最接近的实现是future.standard_library.RenameImport。它看起来并不像是从Python 3到2的完整实现importlib的一个backport。

在这个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'。我想要的是当你这样做的时候改变发生的事情,特别是当你导入A时运行'import B'。这可以通过'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.请注意,在Python 3中为你工作的imp.load_module()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子模块也是如此。我试图实现的是在导入'PyQt4.QtCore'时导入'SiQt.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的其他部分,可能会在名称字符串中用'SiQt'替换'PyQt4',然后调用默认机制让它自己加载'SiQt'。如果我错了,请提供一些用于钩子的代码,以便更好地理解您正在尝试完成的内容。 – Nikita

3

当找到作为包的一部分的模块(如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