【问题标题】:cx_Freeze - Preventing including unneeded packagescx_Freeze - 防止包含不需要的包
【发布时间】:2015-02-01 13:58:45
【问题描述】:

我使用 PyQt4 编写了一个小型 Python 程序。现在,我想使用 cx_Freeze 创建一个独立的应用程序。一切正常 - cx_Freeze 自动包含所有必要的模块;生成的 exe 有效。

唯一的问题是 cx_Freeze 将大量不需要的模块打包到独立模块中。即使我只使用 QtCore 和 QtGui,也包括 sqlite3、QtNetwork 或 QtScript 等模块。令人惊讶的是,我还在生成的文件夹中找到了 PyQt5 dll。在我看来,好像 cx_Freeze 使用了我安装的所有 PyQt 包。结果是一个 200Mb 的程序——尽管我只写了一个小脚本。

如何防止这种行为?

我使用以下 setup.py:

import sys
from cx_Freeze import setup, Executable

setup(
    name="MyProgram",
    version="0.1",
    description="MyDescription",
    executables=[Executable("MyProgram.py", base = "Win32GUI")],
)

我尝试明确排除一些包(虽然排除所有未使用的 Qt 模块很麻烦)添加以下代码:

build_exe_options = {"excludes": ["tkinter", "PyQt4.sqlite3",
                              "PyQt4.QtOpenGL4", "PyQt4.QtSql"]}

但仍然使用上层模块。我也试过了

build_exe_options = {"excludes": ["tkinter", "PyQt4.sqlite3",
                              "QtOpenGL4", "QtSql"]}

结果相同。

除了 nedless 的 Qt 包之外,我还发现了类似“imageformats”、“tcl”和“tk”等名称的 unnedless 文件夹。我怎样才能包含需要的文件,以使独立文件夹和安装程序尽可能小?

我用谷歌搜索了这个问题几个小时,但只找到this thread 这对我没有帮助。

我在 Windows 8 上运行 python 3.4.2 amd64。

我很高兴每一种解决方案都能以合理的尺寸“独立”为我提供所需的结果。我也尝试了 pyqtdeploy 但遇到了错误:QT 中的未知模块(但这是一个不同的问题)。

编辑:

我正在使用两个模块。一个是由 uic 创建的 GUI 类,“MyProgramGUIPreset”。在这个文件中有以下导入命令:

from PyQt4 import QtCore, QtGui
from matplotlibwidget import MatplotlibWidget

在主模块中,我执行以下导入:

import MyProgramGUIPreset
import numpy as np
from PyQt4.QtGui import QApplication, QMainWindow, QMessageBox
import sys
from math import *

也许这有助于找出问题所在。

【问题讨论】:

  • 一般来说,“排除”是告诉它你不需要的方式。我猜你的脚本正在使用类似 matplotlib 的东西,它具有不同 GUI 的后端 - cx_Freeze 无法判断将使用哪一个,因此它会尝试将它们全部包含在内。如果你能找出是什么模块导致了问题,排除它也应该去掉很多其他模块。
  • @ThomasK:感谢您的建议!事实上,我正在使用 matplotlibwidget。我试图排除它:build_exe_options = {"excludes": ["tkinter", "matplotlibwidget"]} 但没有任何改变。您对我如何找出导致问题的模块有什么提示吗?我也想知道我是否正确使用了“排除”命令。是否有可能排除明确使用的模块?如果 matplotlibwidget 导致问题:我可以在不遗漏小部件的情况下解决问题吗?什么是合适的解决方案?
  • 您可能希望排除所有未使用的后端 matplotlib.backends.backend_foo(即除 Qt4 之外的所有后端)。看起来您可能没有将 build_exe_options 传递给 setup - 请参阅此处的示例:cx-freeze.readthedocs.org/en/latest/distutils.html
  • 谢谢你,@ThomasK!确实,我忘记将build_exe_options 传递给设置方法。现在我添加了options = {"build_exe": build_exe_options}, 并且排除工作正常。也就是说,我可以传递我想要排除的所有模块的列表。但我仍然想知道如何一次排除 all 不需要的模块。我试过build_exe_options = {"excludes": ["matplotlib.backends.backend_foo"]},它没有做任何事情。我是否正确理解了您的建议,Thomas K?还是我必须在我的代码中以不同的方式包含matplotlib.backends.backend_foo
  • 由于我的英语知识有限,我很确定我误解了您的backend_foo(尤其是“foo”这个词)。是的,我想排除除 (!) matplotlib.backends.backend_qt4agg 之外的所有 matplotlib 后端。如果不列出所有 26 种可能性,我怎样才能做到这一点?我可以使用正则表达式吗?排除 matplotlib.backends 会使文件尽可能小。我试图排除上述内容,然后包括 matplotlib.backends.backend_qt4agg 但这给了我一个例外,即没有同名的模块...

标签: python deployment pyqt cx-freeze


【解决方案1】:

“排除”命令不起作用的原因是我忘记在设置中包含构建选项。将相应的行添加到代码中后,不包括作品:

from cx_Freeze import setup, Executable
import sys

# exclude unneeded packages. More could be added. Has to be changed for
# other programs.
build_exe_options = {"excludes": ["tkinter", "PyQt4.QtSql", "sqlite3", 
                                  "scipy.lib.lapack.flapack",
                                  "PyQt4.QtNetwork",
                                  "PyQt4.QtScript",
                                  "numpy.core._dotblas", 
                                  "PyQt5"],
                     "optimize": 2}

# Information about the program and build command. Has to be adjusted for
# other programs
setup(
    name="MyProgram",                           # Name of the program
    version="0.1",                              # Version number
    description="MyDescription",                # Description
    options = {"build_exe": build_exe_options}, # <-- the missing line
    executables=[Executable("MyProgram.py",     # Executable python file
                            base = ("Win32GUI" if sys.platform == "win32" 
                            else None))],
)

这将程序大小从 230MB 减少到 120MB。尽管如此,我没有找到排除所有不需要的包的好方法。通过反复试验(以测试方式删除构建文件夹中的最大文件),我确定了可以排除哪些类。

我尝试了 matplotlib 后端是否会导致问题,最后发现不是这种情况。尽管如此,如果有人需要代码来排除特定文件夹中特定名称方案的所有模块,除了一些特殊的模块,他可以根据自己的需要调整以下内容:

mplBackendsPath = os.path.join(os.path.split(sys.executable)[0],
                        "Lib/site-packages/matplotlib/backends/backend_*")

fileList = glob.glob(mplBackendsPath)

moduleList = []

for mod in fileList:
    modules = os.path.splitext(os.path.basename(mod))[0]
    if not module == "backend_qt4agg":
        moduleList.append("matplotlib.backends." + modules)

build_exe_options = {"excludes": ["tkinter"] + moduleList, "optimize": 2}

我会很高兴看到更优雅的解决方案。仍然欢迎进一步的想法。不过,我认为问题已经解决了。

【讨论】:

  • uic 需要QtNetwork 吗?我的应用没有这个模块和 qtwebkit 没有启动
【解决方案2】:

我在一个非常简单的 PyQt4 Gui 上遇到了类似的问题,该数据库用于一个小型数据库,其中程序为 58Mb 的少量代码,问题是整个 PyQt4 文件夹都包含在程序中。

here 文章提到在您的选项中使用 zip_include_packages 来排除文件或压缩它们以减小文件大小。

我排除了整个 PyQt4 文件夹,然后包含了我需要的位,如下所示,它自动将整个包减少到 16Mb

options = {
'build_exe': {
    'packages':packages,
    'zip_include_packages':'PyQt4',
    'includes':['PyQt4.QtCore','PyQt4.QtGui','sqlite3','sys','os'],
},

不确定这是不是正确的方法,但似乎对我的程序没有负面影响

【讨论】:

  • 我尝试使用 PyQt5 进行此操作,虽然它有效并且确实显着减小了构建大小,但应用程序的质量明显较低(几乎就像它停留在经典的 Windows 主题上一样)。此外,包括特定的 PyQt5 “位”似乎对视觉质量和构建大小没有影响。
【解决方案3】:

这就是我将可执行文件优化到最小文件大小的方式

from cx_Freeze import setup, Executable
import subprocess
import sys


NAME = 'EXE NAME'
VERSION = '1.0'
PACKAGES = ['pygame', ('import_name', 'package_name')]
# if names are same just have a string not a tuple
installed_packages = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']).decode('utf-8')
installed_packages = installed_packages.split('\r\n')
EXCLUDES = {pkg.split('==')[0] for pkg in installed_packages if pkg != ''}
EXCLUDES.add('tkinter')
for pkg in PACKAGES:
    if type(pkg) == str: EXCLUDES.remove(pkg)
    else: EXCLUDES.remove(pkg[1])


executables = [Executable('main.py', base='Win32GUI', icon='Resources/Jungle Climb Icon.ico', targetName=NAME)]

setup(
    name=NAME,
    version=VERSION,
    description=f'{NAME} Copyright 2019 AUTHOR',
    options={'build_exe': {'packages': [pkg for pkg in PACKAGES if type(pkg) == str else pkg[0]],
                           'include_files': ['FOLDER'],
                           'excludes': EXCLUDES,
                           'optimize': 2}},
    executables=executables)

【讨论】:

  • 嗯...看起来这不可靠。 tkinter 未显示在包列表中(如您所述,手动添加该包)并且包“serial”在列表中显示为“pyserial”。如果我将 'pyserial' 放入 PACKAGES,我会得到 "ImportError: No module named 'pyserial' 但是如果我将 'serial' 放入 PACKAGES,我会得到 KeyError: 'serial' 。似乎是一种新颖的方法,但有没有更好的方法获取在两个语句中一致的实际包名称?
  • @Trashman 我已经修改了我的代码来处理双命名包。不过我还没有测试过。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
相关资源
最近更新 更多