WxPython:PyInstaller 失败,没有名为 _core_ 的模块
WxPython: PyInstaller fails with No module named _core_
我正在使用 PyInstaller 将我的 wxpython (3.0.2.0) 应用程序转换为二进制文件。在 Ubuntu 12.04 上构建和执行时,二进制文件工作正常。但是,如果我在 Ubuntu 14.04 上构建,我会收到以下错误。 (当我直接启动 python 脚本时,应用程序可以工作,即 python my_application.py 即使在 Ubuntu 14.04 中也是如此)。知道使用 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 版本上——您需要使用 develop
版本。此问题已被发现并记录在 PyInstaller Github issue.
中
要安装最新版本并纠正 - 在命令提示符下键入:
$ pip install git+https://github.com/pyinstaller/pyinstaller
这直接从 github 安装最新版本的 pyinstaller(这个 branch on github. Until recently, PyInstaller had a separate python3
branch, but this has been merged back into the develop
branch. If you need to use Python 3.x, you will need this branch - 通过将 @develop
附加到 pip install
命令来获得这个)
上述方法依赖于您在系统上安装了 git
以获取 pyinstaller 代码(我想现在对于开发人员来说很可能)。如果没有,您可以
- 使用
apt-get install git
安装 git(您可能需要 sudo
)
- 下载 pyinstaller-develop zip 文件(here) and install manually. Note as per the wiki as of Oct 2014, 这应该支持 2.7 和 3.x。
就个人而言 - 我更喜欢选项 1,因为您可以避免自己从压缩源代码树构建的所有潜在问题。
测试
我在 Ubuntu 14.04、64 位、wxpython 3.0.2.0 和 python 2.7.6 上测试了这个,使用了来自 wxPython 网页的简单 "Hello world" app . OP 的问题恰好在安装 pyinstaller 开发版之前重现。安装开发版本后,应用程序正确构建并 运行 作为可执行文件。
使用 pip 与 git 的文档 - 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_importers
的 BuiltinImporter
、FrozenImporter
和 CExtensionImporter
的实例附加到 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.运行时挂钩
使用 运行time hooks 可以在不更改代码的情况下解决问题。这是修复 'WxPython' 问题的快速修复。
这个 运行time hook 改变了 CExtensionImporter
实例的一些私有属性。要使用这个钩子,将 --runtime-hook=wx-run-hook.py
赋给 pyinstaller
。
wx-运行-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-运行-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
returns类型imp.C_EXTENSION
的后缀不止一个,而且只有找到一个模块才能提前知道正确的,所以我把它们都存储在列表 self._c_ext_tuples
。正确的后缀设置在 self._suffix
中,它与 self._c_ext_tuple
一起通过 load_module
方法使用,如果找到模块则从 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
我正在使用 PyInstaller 将我的 wxpython (3.0.2.0) 应用程序转换为二进制文件。在 Ubuntu 12.04 上构建和执行时,二进制文件工作正常。但是,如果我在 Ubuntu 14.04 上构建,我会收到以下错误。 (当我直接启动 python 脚本时,应用程序可以工作,即 python my_application.py 即使在 Ubuntu 14.04 中也是如此)。知道使用 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 版本上——您需要使用 develop
版本。此问题已被发现并记录在 PyInstaller Github issue.
要安装最新版本并纠正 - 在命令提示符下键入:
$ pip install git+https://github.com/pyinstaller/pyinstaller
这直接从 github 安装最新版本的 pyinstaller(这个 branch on github. Until recently, PyInstaller had a separate python3
branch, but this has been merged back into the develop
branch. If you need to use Python 3.x, you will need this branch - 通过将 @develop
附加到 pip install
命令来获得这个)
上述方法依赖于您在系统上安装了 git
以获取 pyinstaller 代码(我想现在对于开发人员来说很可能)。如果没有,您可以
- 使用
apt-get install git
安装 git(您可能需要sudo
) - 下载 pyinstaller-develop zip 文件(here) and install manually. Note as per the wiki as of Oct 2014, 这应该支持 2.7 和 3.x。
就个人而言 - 我更喜欢选项 1,因为您可以避免自己从压缩源代码树构建的所有潜在问题。
测试
我在 Ubuntu 14.04、64 位、wxpython 3.0.2.0 和 python 2.7.6 上测试了这个,使用了来自 wxPython 网页的简单 "Hello world" app . OP 的问题恰好在安装 pyinstaller 开发版之前重现。安装开发版本后,应用程序正确构建并 运行 作为可执行文件。
使用 pip 与 git 的文档 - 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_importers
的 BuiltinImporter
、FrozenImporter
和 CExtensionImporter
的实例附加到 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.运行时挂钩
使用 运行time hooks 可以在不更改代码的情况下解决问题。这是修复 'WxPython' 问题的快速修复。
这个 运行time hook 改变了 CExtensionImporter
实例的一些私有属性。要使用这个钩子,将 --runtime-hook=wx-run-hook.py
赋给 pyinstaller
。
wx-运行-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-运行-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
returns类型imp.C_EXTENSION
的后缀不止一个,而且只有找到一个模块才能提前知道正确的,所以我把它们都存储在列表 self._c_ext_tuples
。正确的后缀设置在 self._suffix
中,它与 self._c_ext_tuple
一起通过 load_module
方法使用,如果找到模块则从 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