WxPython:PyInstaller失败,无模块名为_core_

我使用PyInstaller将我的wxpython(3.0.2.0)应用程序转换为二进制文件。 在Ubuntu 12.04上构build并执行这些二进制文件时可以正常工作。 但是,如果我build立在Ubuntu 14.04,我得到以下错误。 (当我直接启动python脚本,即使在Ubuntu 14.04中,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 spec文件看起来像这样:

 ... 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版本的基本问题是 – 你需要在develop版本上。 这个问题已经在PyInstaller的Github问题上看到并且被记录下来 。

要安装最新版本并纠正 – 请在命令提示符下输入:

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

这直接从github安装最新版本的pyinstaller(这个分支在github上)直到最近,PyInstaller还有一个单独的python3分支,但是这已经被合并到了develop分支中 ,如果你需要使用Python 3.x,你需要这个分支 – 通过将@develop附加到pip install命令来获得)

上面的方法依赖于你在你的系统上安装git来获取pyinstaller代码(我猜这些日子很可能是开发者)。 如果没有,你也可以

  1. 安装git使用apt-get install git (你可能需要sudo
  2. 下载pyinstaller-develop zip文件( 这里 )并手动安装。 根据截至2014年10月的维基注意,这应该支持2.7和3.x.

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

测试

我使用了wxPython网页中简单的“Hello world”应用程序 ,在Ubuntu 14.04,64位,使用python 2.7.6测试了wxpython 3.0.2.0。 在安装pyinstaller开发版之前,OP的问题已经完全复制了。 安装开发版本后,应用程序正确构建并作为可执行文件运行。


与git一起使用pip的文档https://pip.pypa.io/en/latest/reference/pip_install.html#git

从您的问题中不清楚您的Ubuntu 12.04安装与14.04版本使用的PyInstaller版本。 看起来你在12.04版本上的版本并没有象在14.04版本上安装的标准版本那样出现问题。

如果PyInstaller开发版本由于某种原因而不被期望,这里进行一些修正。

来自PyInstaller.loader.pyi_importersBuiltinImporterFrozenImporterCExtensionImporterPyInstaller.loader.pyi_importers被附加到sys.meta_path 。 而find_module方法依次被调用,直到其中一个模块导入成功。

CExtensionImporter仅选择C扩展的许多后缀之一加载,fe wx._core_.i386-linux-gnu.so 。 这就是为什么它无法加载C扩展wx._core_.so

Buggy代码;

 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_suffixes返回多个imp.get_suffixes类型的后缀,并且在找到模块之前无法预先知道正确的后缀,因此我将它们全部存储在列表self._c_ext_tuples 。 右后缀在self._suffix设置,如果找到该模块,则由find_module方法通过load_module方法与self._c_ext_tuple一起使用。

 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