导入PyQt4.QtCore的钩子

rth*_*rth 10 python pyqt python-2.7 python-sip

我试图通过一个类似于这个问题的方法来设置一些导入钩子.为此,我需要定义两个函数,如上面的链接所述.这是我的功能,sys.meta_pathfind_moduleload_moduleload_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
Run Code Online (Sandbox Code Playgroud)

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

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

mod = load_module(name, path)
Run Code Online (Sandbox Code Playgroud)

返回,

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
Run Code Online (Sandbox Code Playgroud)

相同的代码适用于Python 3.4(虽然imp已经被弃用,importlib理想情况下应该在那里使用).

我想这与SIP动态模块初始化有关.我还应该尝试使用Python 2.7吗?

注意:这适用于PyQt4PyQt5.

编辑:这可能与这个问题有关,的确如此

cd /usr/lib64/python2.7/site-packages/PyQt4
python2 -c 'import QtCore'
Run Code Online (Sandbox Code Playgroud)

失败并出现同样的错误.我还不确定会有什么方法......

Edit2:关于@Nikita对具体用例示例的请求,我想要做的是重定向导入,所以当一个人做的时候import A,会发生什么import B.人们确实可以认为,为此,进行模块重命名find_spec/find_module然后使用默认值就足够了load_module.但是,目前还不清楚load_module在Python 2中哪里可以找到默认实现.我发​​现的类似的最接近的实现是future.standard_library.RenameImport.它看起来并不像importlibPython 3到2 的完整实现的后端口.

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

Nik*_*ita 4

UPD:这部分在答案更新后并不真正相关,所以请参阅下面的 UPD。

为什么不直接使用importlib.import_modulePython 2.7 和 Python 3 中都可用的 :

#test.py

import importlib

mod = importlib.import_module('PyQt4.QtCore')
print(mod.__file__)
Run Code Online (Sandbox Code Playgroud)

在 Ubuntu 14.04 上:

$ python2 test.py 
/usr/lib/python2.7/dist-packages/PyQt4/QtCore.so
Run Code Online (Sandbox Code Playgroud)

由于它是一个动态模块,正如错误中所述(实际文件是QtCore.so),因此也可以看看imp.load_dynamic.

另一个解决方案可能是强制执行模块初始化代码,但在我看来,这太麻烦了,所以为什么不直接使用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
Run Code Online (Sandbox Code Playgroud)

目前无法测试上面的代码,请自行尝试。

PS 请注意imp.load_module(),在 Python 3 中为您工作的 ,自 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__
Run Code Online (Sandbox Code Playgroud)

输出:

<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
Run Code Online (Sandbox Code Playgroud)

shelve重命名为pickle,并且pickle默认使用变量 name 导入机器shelve