【问题标题】:Import hooks for PyQt4.QtCorePyQt4.QtCore 的导入钩子
【发布时间】:2016-08-12 09:29:15
【问题描述】:

我正在尝试设置一些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 的具体用例示例的请求,我想做的是重定向导入,所以当有人这样做时 @987654344 @,发生的是import B。确实可以认为,为此在find_spec/find_module 中进行模块重命名就足够了,然后使用默认的load_module。但是,尚不清楚在 Python 2 中在哪里可以找到默认的 load_module 实现。我发​​现的最接近的类似实现是 future.standard_library.RenameImport。从 Python 3 到 2 似乎没有 importlib 的完整实现的反向移植。

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

【问题讨论】:

  • 如果它可能有用,为我正在尝试做的事情提供一些一般背景,请参阅SiQt 包,this github issue 中讨论了这个问题。
  • 我真的不明白你的问题,但__import__('PyQt4.QtCore') 有什么问题。会导致无限递归吗?
  • @danidee __import__('A') 没什么问题,但它相当于使用import A。我想要的是改变当你这样做时会发生什么,特别是当你 import A 时运行 import B。这可以通过 sys.meta_path 中的导入挂钩来完成,但它们需要较低级别的函数,例如 imp.load_module
  • @rth,确实在 Python 2.7 中关于 imporylib 的文档中写道:“此模块是功能更齐全的同名包中可用功能的一小部分来自 Python 3.1,它提供了完整的导入实现。”PEP302 中有关于自定义导入的想法,我会看看它并在答案更新中分享我的想法。

标签: python python-2.7 pyqt python-sip


【解决方案1】:

当找到像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

【讨论】:

    【解决方案2】:

    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

    UPDpkgutil 中有一些内容,可能会有所帮助。我在评论中所说的,尝试像这样修改你的查找器:

    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
    

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

    附:请注意,在 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

    【讨论】:

    • 我同意你的两个第一个想法,不幸的是它们不起作用,我之前尝试过。 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
    • 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,通过您提供的有关导入挂钩的链接,它表示元路径查找器将为路径的每个部分递归调用 find_spec/find_module。例如。 mpf.find_spec("PyQt4", None, None) 然后又是一个mpf.find_spec("PyQt4.QtCore", PyQt4.__path__, None)。因此,如果您要代替 find_spec 或 mpf 的其他部分进行挂钩,可以将名称字符串中的 PyQt4 替换为 SiQt,然后调用默认机制以使其自行加载 SiQt。如果我错了,请提供一些用于挂钩的代码,以更好地理解您要完成的工作。
    • 我同意使用load_module 的默认机制会很好。请参阅上述问题中的 Edit2。
    • @rth,如果钩子不起作用,您可以包装__import__,查看答案更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 1970-01-01
    • 2017-01-27
    • 2014-07-05
    • 1970-01-01
    相关资源
    最近更新 更多