instalar - WxPython: PyInstaller falla con ningún módulo llamado_core_
wxpython fedora (2)
Estoy convirtiendo mi aplicación wxpython (3.0.2.0) a los binarios que usan PyInstaller. Los binarios funcionan bien cuando se construyen y ejecutan en Ubuntu 12.04. Sin embargo, si construyo en Ubuntu 14.04, aparece el siguiente error. (La aplicación funciona cuando ejecuto el script 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 "<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_**
El archivo de especificaciones de My PyInstaller tiene este aspecto:
...
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'')
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 anexan a sys.meta_path
. Y el método find_module
del que se llaman en orden hasta que uno de ellos tenga é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 no puede cargar la extensión C wx._core_.so
.
Código 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 de tiempo de ejecución
Es posible solucionar el problema sin cambiar el código utilizando los ganchos de tiempo de ejecución. Esta es una solución rápida que soluciona los problemas de ''WxPython''.
Este enganche de tiempo de ejecución cambia algunos atributos privados de instancia de CExtensionImporter
. Para usar este gancho, brinde --runtime-hook=wx-run-hook.py
a 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]
. Por lo tanto, debería funcionar en la mayoría de las situaciones. Usar 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)
Como 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
todos en una lista self._c_ext_tuples
. El sufijo correcto se establece en self._suffix
, que se usa junto con self._c_ext_tuple
mediante el método load_module
, del 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
Fundamentalmente, el problema es con 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 de github (esta rama en github .) Hasta hace poco, PyInstaller tenía una rama de python3
separada, pero esto se ha fusionado de nuevo en la rama de develop
. Si necesita utilizar Python 3.x, necesitará esta rama - obtén esto agregando @develop
al comando de pip install
)
El método anterior depende de que hayas instalado git
en tu sistema para obtener el código de pyinstaller (creo que es bastante probable para un desarrollador). Si no, puedes
- instale git usando
apt-get install git
(puede que necesitesudo
eso) - Descargue el archivo zip de pyinstaller-develop ( aquí ) e instálelo manualmente. Tenga en cuenta que según la wiki a partir de octubre de 2014, esto debería ser compatible con 2.7 y 3.x.
Personalmente, prefiero la opción 1 ya que evita todos los posibles problemas de compilación desde 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 sencilla aplicación "Hello world" de la página web de wxPython. El problema del OP se reprodujo exactamente antes de instalar la versión de desarrollo de pyinstaller. Después de instalar la versión de desarrollo, la aplicación se compiló correctamente y se ejecutó como un ejecutable.
Documentación sobre el uso de pip con git - https://pip.pypa.io/en/latest/reference/pip_install.html#git
No está claro a partir de su pregunta qué versiones de PyInstaller está utilizando en su instalación Ubuntu 12.04 contra 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.