WxPython: PyInstaller does not work with No module named _core_

I am converting my wxpython application (3.0.2.0) to binaries using PyInstaller. Binary files work great when building and executing on Ubuntu 12.04. However, if I describe Ubuntu 14.04, I get the following error. (The application works when I run the python script directly, e.g. python my_application.py, even in Ubuntu 14.04). Any idea what might be missing when packing an application using 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_** 

The My PyInstaller specification file is as follows:

 ... 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') 
+7
python ubuntu pyinstaller wxpython
source share
2 answers

Basically the problem is with the version of PyInstaller - you need to be in the develop version. This issue has been noticed and documented in the PyInstaller Github issue .

To install the latest version and fix - at the command prompt, type:

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

This directly installs the latest version of pyinstaller from github (this is a branch on github . Until recently, PyInstaller had a separate python3 branch python3 but it was merging back into the develop branch . If you need to use Python 3.x, you need this branch - get this by adding @develop to pip install command)

The above method is based on using git git on your system to get the pyinstaller code (most likely for developers these days, I think). If not, you can either

  • install git with apt-get install git (you may need sudo )
  • download the zip file pyinstaller-development zip ( here ) and install it manually. Please note that according to the wiki from October 2014 this should support versions 2.7 and 3.x.

Personally - I prefer option 1, since you avoid all potential problems with building from a tree with an archived source.

Testing

I tested this on Ubuntu 14.04, 64 bit, wxpython 3.0.2.0 using python 2.7.6 using the simple "Hello world" application from the wxPython web page. The OP problem reproduces exactly prior to installing the pyinstaller development version. After installing the development version, the application is built correctly and runs as an executable file.


Documentation of using pip with git - https://pip.pypa.io/en/latest/reference/pip_install.html#git

From your question it is not clear which versions of PyInstaller you use on your installation of Ubuntu 12.04 and version 14.04. It seems that the version you have 12.04 does not show the same problem as the standard version installed on 14.04.

+8
source share

If the PyInstaller development version is not needed for any reason, there is some fix.

An instance of BuiltinImporter , FrozenImporter and CExtensionImporter from PyInstaller.loader.pyi_importers added to sys.meta_path . And find_module method is called in order until one of them succeeds when the module is imported.

CExtensionImporter selects only one of the many suffixes for the C extension to load, i.e. wx._core_.i386-linux-gnu.so . Therefore, you cannot load the C extension wx._core_.so .

Buggy code;

 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 

Fix

1. Runtime hooks
You can fix the problem without changing the code using runtime hooks. This is a quick fix that fixes WxPython issues.
This runtime hook changes some of the private attributes of the CExtensionImporter instance. To use this hook, give --runtime-hook=wx-run-hook.py pyinstaller .

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] 

This second run-time bit completely replaces the object in sys.meta_path[-1] . Therefore, it should work in most situations. Use as 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 import lock. imp.release_lock() raise # Raise the same exception again. # Release the interpreter 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. Change code

 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) 

Since imp.get_suffixes returns more than one suffix for type imp.C_EXTENSION , and the right one cannot be known in advance until a module is found, I will save them all in the list self._c_ext_tuples . The correct suffix is ​​set to self._suffix , which is used with the self._c_ext_tuple method by the self._c_ext_tuple method, from the find_module method, if the module is found.

 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 
+4
source share

All Articles