WxPython: PyInstaller falla con ningún módulo llamado _core_

Estoy convirtiendo mi aplicación wxpython (3.0.2.0) a binarios utilizando PyInstaller. Los binarios funcionan bien cuando se construyen y ejecutan en Ubuntu 12.04. Sin embargo, si compilo en Ubuntu 14.04, aparece el siguiente error. (La aplicación funciona cuando ejecuto el script de Python directamente, es decir python my_application.py incluso en Ubuntu 14.04). ¿Alguna idea de lo que podría faltar al empaquetar la aplicación utilizando PyInstaller?

$ ./my_application Traceback (most recent call last): File "", line 22, in  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  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  **ImportError: No module named _core_** 

Mi archivo de especificaciones de PyInstaller se ve así:

 ... 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') 

Fundamentalmente, el problema está en la versión de PyInstaller: debe estar en la versión de develop . Este problema se ha visto y está documentado en un problema de PyInstaller Github .

Para instalar la última versión y rectificar, en el símbolo del sistema escriba:

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

Esto instala directamente la última versión de pyinstaller desde github (esta twig en github . Hasta hace poco, PyInstaller tenía una twig separada de python3 , pero esto se ha vuelto a combinar en la twig develop . Si necesita usar Python 3.x, necesitará esta twig – obtenga esto agregando @develop al comando de pip install )

El método anterior se basa en que tengas git instalado en tu sistema para obtener el código del progtwig de instalación (lo cual es bastante probable para un desarrollador en estos días, supongo). Si no, puedes hacerlo

  1. instale git utilizando apt-get install git (es posible que necesite sudo )
  2. descargue el archivo zip de pyinstaller-develop ( aquí ) e instálelo manualmente. Nota según la wiki a partir de octubre de 2014, esto debería admitir 2.7 y 3.x.

Personalmente, prefiero la opción 1, ya que usted mismo evita todos los problemas potenciales de comstackción a partir de un árbol fuente comprimido.

Pruebas

Probé esto en Ubuntu 14.04, 64 bit, wxpython 3.0.2.0 con python 2.7.6, usando la aplicación simple “Hello world” de la página web wxPython. El problema del OP se reproduce exactamente antes de instalar la versión de desarrollo de pyinstaller. Después de instalar la versión desarrollada, la aplicación se construyó correctamente y se ejecutó como un ejecutable.


Documentación del uso de pip con git – https://pip.pypa.io/en/latest/reference/pip_install.html#git

No queda claro en su pregunta qué versiones de PyInstaller está utilizando en su instalación de Ubuntu 12.04 en comparación con la versión 14.04. Parece que la versión que tiene en 12.04 no presenta el mismo problema que la versión estándar instalada en 14.04.

Si la versión de desarrollo de PyInstaller no es deseada por alguna razón, aquí hay una solución.

La instancia de BuiltinImporter , FrozenImporter y CExtensionImporter de PyInstaller.loader.pyi_importers se anexa a sys.meta_path . Y el método find_module se llama en orden hasta que uno de ellos tiene éxito cuando se importa un módulo.

CExtensionImporter elige solo uno de los muchos sufijos para cargar la extensión C, fe wx._core_.i386-linux-gnu.so . Es por eso que falla al cargar la extensión C wx._core_.so .

Código de 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 

Fijar;

1. Ganchos en tiempo de ejecución
Es posible solucionar el problema sin cambiar el código utilizando enganches de tiempo de ejecución. Esta es una solución rápida que corrige los problemas ‘WxPython’.
Este gancho de tiempo de ejecución cambia algunos atributos privados de la instancia de CExtensionImporter . Para usar este gancho, --runtime-hook=wx-run-hook.py al 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] 

Este segundo gancho de tiempo de ejecución reemplaza completamente el objeto en sys.meta_path[-1] . Así debería funcionar en la mayoría de las situaciones. Utilice como 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. Cambio de código

 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) 

Debido a que imp.get_suffixes devuelve más de un sufijo para el tipo imp.C_EXTENSION y el derecho no se puede conocer de antemano hasta que se encuentre un módulo, los self._c_ext_tuples en una lista self._c_ext_tuples . El sufijo derecho se establece en self._suffix , que se usa junto con self._c_ext_tuple mediante el método load_module , desde el método find_module si se encuentra el módulo.

 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