10

我正在尝试以与此 SO question有点相似的方法设置一些导入挂钩。为此,我需要定义两个函数,如上面链接中所述。这是我的功能,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

这适用于大多数模块,但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

编辑:这可能确实与这个问题有关,

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

失败并出现相同的错误。我仍然不确定有什么方法可以解决它...

Edit2:根据@Nikita对具体用例示例的请求,我想做的是重定向导入,所以当一个人这样做时import A,会发生什么import B。确实可以认为,为此进行模块重命名find_spec/find_module然后使用 default就足够了load_module。但是,目前尚不清楚在 Python 2 中哪里可以找到默认load_module实现。我发​​现的最接近的类似实现是future.standard_library.RenameImport. 看起来没有importlib从 Python 3 到 2 的完整实现的反向移植。

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

4

2 回答 2

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

现在无法测试上面的代码,所以请自己尝试一下。

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__

输出:

<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

于 2016-04-22T09:15:36.193 回答
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
于 2016-04-28T21:39:26.233 回答