【问题标题】:WxPython: PyInstaller fails with No module named _core_WxPython:PyInstaller 失败,没有名为 _core_ 的模块
【发布时间】:2015-11-16 06:08:58
【问题描述】:

我正在使用 PyInstaller 将我的 wxpython (3.0.2.0) 应用程序转换为二进制文件。在 Ubuntu 12.04 上构建和执行时,二进制文件可以正常工作。但是,如果我在 Ubuntu 14.04 上构建,我会收到以下错误。 (即使在 Ubuntu 14.04 中,当我直接启动 python 脚本即 python my_application.py 时,该应用程序也能正常工作)。知道使用 PyInstaller 打包应用程序时可能缺少什么吗?

$ ./my_application 
Traceback (most recent call last):
  File "<string>", line 22, in <module>
  File "/usr/local/lib/python2.7/dist-packages/PyInstaller/loader/pyi_importers.py", line 270, in load_module
    exec(bytecode, module.__dict__)
  File "/local/workspace/my_application/out00-PYZ.pyz/wx", line 45, in <module>
  File "/usr/local/lib/python2.7/dist-packages/PyInstaller/loader/pyi_importers.py", line 270, in load_module
    exec(bytecode, module.__dict__)
  File "/local/workspace/my_application/out00-PYZ.pyz/wx._core", line 4, in <module>
**ImportError: No module named _core_**

我的 PyInstaller 规范文件如下所示:

...
pyz = PYZ(a.pure)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='my_application',
          debug=False,
          onefile = True,
          strip=None,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=None,
               upx=True,
               name='my_application')

【问题讨论】:

  • 我目前无法从这里重现问题(不同的操作系统),但您是否尝试过使用 pyinstaller 的替代方案? (例如 cx_freeze)?
  • Python 交互式 shell 上执行 from wx import _core; print _core.__file__ 会得到什么?
  • @Nemo - 我的回答有什么问题可以让我更清楚/帮助解决吗?

标签: python ubuntu wxpython ubuntu-14.04 pyinstaller


【解决方案1】:

从根本上说问题出在 PyInstaller 版本上 - 您需要使用 develop 版本。此问题已被发现并记录在 PyInstaller Github issue

要安装最新版本并纠正 - 在命令提示符下键入:

$ pip install git+https://github.com/pyinstaller/pyinstaller

这直接从github安装最新版本的pyinstaller(这个branch on github。直到最近,PyInstaller有一个单独的python3分支,但是这个一直是merged back into the develop branch。如果你需要使用Python 3.x,你将需要this branch - 通过将@develop 附加到pip install 命令来获取)

上述方法依赖于您在系统上安装 git 来获取 pyinstaller 代码(我猜现在对于开发人员来说很可能)。如果没有,你也可以

  1. 使用apt-get install git 安装git(你可能需要sudo 那个)
  2. 下载 pyinstaller-develop zip 文件 (here) 并手动安装。请注意,根据 wiki as of Oct 2014,,这应该支持 2.7 和 3.x。

就个人而言 - 我更喜欢选项 1,因为您可以避免自己从压缩源代码树构建的所有潜在问题。

测试

我使用来自 wxPython 网页的简单 "Hello world" app 在 Ubuntu 14.04、64 位、wxpython 3.0.2.0 和 python 2.7.6 上进行了测试。 OP 的问题在安装 pyinstaller 开发版本之前完全重现。安装开发版本后,应用程序正确构建并作为可执行文件运行。


文档在 git 中使用 pip - https://pip.pypa.io/en/latest/reference/pip_install.html#git

从您的问题中不清楚您在 Ubuntu 12.04 安装和 14.04 版本上使用的 PyInstaller 版本。您在 12.04 上使用的版本似乎与在 14.04 上安装的标准版本没有相同的问题。

【讨论】:

  • @Nemo 只是一个 ping 让你知道我的答案,鉴于你已经发布了慷慨的赏金,我认为这对你来说可能非常重要。
  • Python 3.x 分支的链接目前已断开。
  • @shuttle87 - 这就是链接断开的原因 - 正如聊天中所建议的那样 - 它现在已合并到 develop github.com/pyinstaller/pyinstaller/issues/1417
【解决方案2】:

如果由于某种原因不需要 PyInstaller 开发版本,这里有一些修复。

来自PyInstaller.loader.pyi_importersBuiltinImporterFrozenImporterCExtensionImporter 的实例附加到sys.meta_path。并且 find_module 方法按顺序调用,直到其中一个在导入模块时成功。

CExtensionImporter 仅选择要加载的 C 扩展名的众多后缀之一,f.e. wx._core_.i386-linux-gnu.so。这就是它无法加载 C 扩展 wx._core_.so 的原因。

错误代码;

class CExtensionImporter(object):
    def __init__(self):
        # Find the platform specific suffix. On Windows it is .pyd, on Linux/Unix .so.
        for ext, mode, typ in imp.get_suffixes():
            if typ == imp.C_EXTENSION:
                self._c_ext_tuple = (ext, mode, typ)
                self._suffix = ext  # Just string like .pyd  or  .so
                break

修复;

1.运行时挂钩
使用运行时挂钩可以在不更改代码的情况下解决问题。这是修复“WxPython”问题的快速修复程序。
这个运行时钩子改变了CExtensionImporter 实例的一些私有属性。要使用这个钩子,请将--runtime-hook=wx-run-hook.pypyinstaller

wx-run-hook.py

import sys
import imp

sys.meta_path[-1]._c_ext_tuple = imp.get_suffixes()[1]
sys.meta_path[-1]._suffix = sys.meta_path[-1]._c_ext_tuple[0]

第二个运行时挂钩完全替换了sys.meta_path[-1] 中的对象。所以它应该在大多数情况下工作。用作pyinstaller --runtime-hook=pyinstaller-run-hook.py application.py

pyinstaller-run-hook.py

import sys
import imp

from PyInstaller.loader import pyi_os_path

class CExtensionImporter(object):
    """
    PEP-302 hook for sys.meta_path to load Python C extension modules.

    C extension modules are present on the sys.prefix as filenames:

        full.module.name.pyd
        full.module.name.so
    """
    def __init__(self):
        # TODO cache directory content for faster module lookup without file system access.
        # Find the platform specific suffix. On Windows it is .pyd, on Linux/Unix .so.
        self._c_ext_tuples = [(ext, mode, typ) for ext, mode, typ in imp.get_suffixes() if typ == imp.C_EXTENSION]

        # Create hashmap of directory content for better performance.
        files = pyi_os_path.os_listdir(sys.prefix)
        self._file_cache = set(files)

    def find_module(self, fullname, path=None):
        imp.acquire_lock()
        module_loader = None  # None means - no module found by this importer.

        # Look in the file list of sys.prefix path (alias PYTHONHOME).
        for ext, mode, typ in self._c_ext_tuples:
            if fullname + ext in self._file_cache:
                module_loader = self
                self._suffix = ext
                self._c_ext_tuple = (ext, mode, typ)
                break

        imp.release_lock()
        return module_loader

    def load_module(self, fullname, path=None):
        imp.acquire_lock()

        try:
            # PEP302 If there is an existing module object named 'fullname'
            # in sys.modules, the loader must use that existing module.
            module = sys.modules.get(fullname)

            if module is None:
                filename = pyi_os_path.os_path_join(sys.prefix, fullname + self._suffix)
                fp = open(filename, 'rb')
                module = imp.load_module(fullname, fp, filename, self._c_ext_tuple)
                # Set __file__ attribute.
                if hasattr(module, '__setattr__'):
                    module.__file__ = filename
                else:
                    # Some modules (eg: Python for .NET) have no __setattr__
                    # and dict entry have to be set.
                    module.__dict__['__file__'] = filename

        except Exception:
            # Remove 'fullname' from sys.modules if it was appended there.
            if fullname in sys.modules:
                sys.modules.pop(fullname)
            # Release the interpreter's import lock.
            imp.release_lock()
            raise  # Raise the same exception again.

        # Release the interpreter's import lock.
        imp.release_lock()

        return module

    ### Optional Extensions to the PEP302 Importer Protocol

    def is_package(self, fullname):
        """
        Return always False since C extension modules are never packages.
        """
        return False

    def get_code(self, fullname):
        """
        Return None for a C extension module.
        """
        if fullname + self._suffix in self._file_cache:
            return None
        else:
            # ImportError should be raised if module not found.
            raise ImportError('No module named ' + fullname)

    def get_source(self, fullname):
        """
        Return None for a C extension module.
        """
        if fullname + self._suffix in self._file_cache:
            return None
        else:
            # ImportError should be raised if module not found.
            raise ImportError('No module named ' + fullname)

    def get_data(self, path):
        """
        This returns the data as a string, or raise IOError if the "file"
        wasn't found. The data is always returned as if "binary" mode was used.

        The 'path' argument is a path that can be constructed by munging
        module.__file__ (or pkg.__path__ items)
        """
        # Since __file__ attribute works properly just try to open and read it.
        fp = open(path, 'rb')
        content = fp.read()
        fp.close()
        return content

    # TODO Do we really need to implement this method?
    def get_filename(self, fullname):
        """
        This method should return the value that __file__ would be set to
        if the named module was loaded. If the module is not found, then
        ImportError should be raised.
        """
        if fullname + self._suffix in self._file_cache:
            return pyi_os_path.os_path_join(sys.prefix, fullname + self._suffix)
        else:
            # ImportError should be raised if module not found.
            raise ImportError('No module named ' + fullname)

#This may overwrite some other object
#sys.meta_path[-1] = CExtensionImporter()

#isinstance(object, CExtensionImporter)
#type(object) == CExtensioImporter
#the above two doesn't work here

#grab the index of instance of CExtensionImporter

for i, obj in enumerate(sys.meta_path):
    if obj.__class__.__name__ == CExtensionImporter.__name__:
        sys.meta_path[i] = CExtensionImporter()
        break

2。代码更改

class CExtensionImporter(object):
    def __init__(self):
        # Find the platform specific suffix. On Windows it is .pyd, on Linux/Unix .so.
        self._c_ext_tuples = [(ext, mode, typ) for ext, mode, typ in imp.get_suffixes() if typ == imp.C_EXTENSION]

        files = pyi_os_path.os_listdir(sys.prefix)
        self._file_cache = set(files)

因为imp.get_suffixesimp.C_EXTENSION 类型返回多个后缀,并且在找到模块之前无法提前知道正确的后缀,因此我将它们全部存储在列表self._c_ext_tuples 中。右后缀设置在self._suffix 中,通过load_module 方法与self._c_ext_tuple 一起使用,如果找到模块,则来自find_module 方法。

def find_module(self, fullname, path=None):
    imp.acquire_lock()
    module_loader = None  # None means - no module found by this importer.

    # Look in the file list of sys.prefix path (alias PYTHONHOME).
    for ext, mode, typ in self._c_ext_tuples:
        if fullname + ext in self._file_cache:
            module_loader = self
            self._suffix = ext
            self._c_ext_tuple = (ext, mode, typ)
            break

    imp.release_lock()
    return module_loader

【讨论】:

  • 刚刚检查了这个,很好的答案详细说明了为什么会发生这种情况!
  • @j-richard-snape 谢谢!
猜你喜欢
  • 2018-09-05
  • 2021-04-04
  • 2017-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-17
  • 1970-01-01
  • 2021-01-02
相关资源
最近更新 更多