导入PyQt4.QtCore的钩子

时间:2016-04-19 23:40:24

标签: python python-2.7 pyqt python-sip

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

适用于大多数模块,但在使用Python 2.7时PyQt4.QtCore失败:

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 对具体用例示例的请求,我要做的是重定向导入,所以当一个人{{} 1}},会发生什么是import A。人们确实可以认为,为此,在import B中进行模块重命名然后使用默认的find_spec/find_module就足够了。但是,目前还不清楚在Python 2中哪里可以找到默认的load_module实现。我发​​现类似的最接近的实现是future.standard_library.RenameImport。看起来似乎没有从Python 3到2的完整实现load_module的反向移植。

可以在此gist中找到重现此问题的导入挂钩的最小工作示例。

2 个答案:

答案 0 :(得分:4)

UPD:此部分在回复更新后并不相关,请参阅下面的UPD。

为什么不使用{2.7}和Python 3中提供的importlib.import_module

#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已重命名为picklepickle是默认情况下导入的,其变量名为shelve

答案 1 :(得分: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