将 .py 编译为 .exe 的问题

时间:2021-03-15 14:01:47

标签: python python-3.x pyinstaller

我尝试使用 pyinstaller 将 .py 文件编译为 .exe 文件,但我总是在终端中收到此警告:

c:\users\cpuhv\appdata\local\programs\python\python39\lib\site-packages\setuptools\distutils_patch.py:25: UserWarning: Distutils was imported before Setuptools. This u
sage is discouraged and may exhibit undesirable behaviors or errors. Please use Setuptools' objects directly or at least import Setuptools first.
  warnings.warn(

当我尝试运行 .exe 文件时,我收到一个带有文本的警告窗口:“无法执行脚本”

有人知道如何解决这个问题吗?

4 个答案:

答案 0 :(得分:0)

import sys
import os
import comtypes.client
from pdf2docx import Converter
from docx2pdf import convert
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QToolTip, QFileDialog, QLineEdit, QLabel
from PyQt6.QtGui import QIcon, QFont
from pathlib import Path


home_directory = str(Path.home()).replace("/", "\\")


def name(file):
    path = file
    i = 1
    while os.path.exists(path):
        path = file
        path += " " + str(i)
        i += 1
    return path


class MainGui(QWidget):
    def __init__(self):
        super().__init__()

        self.row1 = 50
        self.row2 = 100
        self.row3 = 175
        self.row4 = 260

        self.button_font = QFont('Verdana', 10)
        self.line = QLineEdit(self)
        self.browse = QPushButton("Durchsuchen", self)
        self.selected_files_count = QLabel("0 Elemente ausgewählt", self)
        self.docx_files = []
        self.pdf_files = []
        self.init_me()

    def init_me(self):
        font = QFont('Verdana', 8)
        font.setBold(True)
        QToolTip.setFont(font)

        pdf_to_docx_button = QPushButton('PDF Dateien auswählen', self)
        pdf_to_docx_button.setFont(self.button_font)
        pdf_to_docx_button.setToolTip('öffen Sie einen Datei-Dialog, um Dateien auszuwählen')
        pdf_to_docx_button.move(50, self.row1)
        pdf_to_docx_button.setFixedWidth(200)
        pdf_to_docx_button.setFixedHeight(35)
        pdf_to_docx_button.clicked.connect(self.pdf_to_docx_button_pressed)

        docx_to_pdf_button = QPushButton('DOCX Dateien auswählen', self)
        docx_to_pdf_button.setFont(self.button_font)
        docx_to_pdf_button.setToolTip('öffen Sie einen Datei-Dialog, um Dateien auszuwählen')
        docx_to_pdf_button.move(300, self.row1)
        docx_to_pdf_button.setFixedWidth(200)
        docx_to_pdf_button.setFixedHeight(35)
        docx_to_pdf_button.clicked.connect(self.docx_to_pdf_button_pressed)

        self.line.move(50, self.row3)
        self.line.setFixedWidth(320)
        self.line.setFixedHeight(30)
        self.line.setFont(self.button_font)
        self.line.setText(home_directory)

        self.browse.move(390, self.row3)
        self.browse.setFont(self.button_font)
        self.browse.setFixedWidth(110)
        self.browse.setFixedHeight(30)
        self.browse.setToolTip('öffen Sie einen Datei-Dialog, um den Ausgabe-Ordner auszuwählen')
        self.browse.clicked.connect(self.browse_button_pressed)

        convert_button = QPushButton('Konvertieren', self)
        convert_button.move(190, self.row4)
        convert_button.setFixedWidth(170)
        convert_button.setFixedHeight(50)
        convert_button.setFont(self.button_font)
        convert_button.clicked.connect(self.convert)

        self.selected_files_count.move(200, self.row2)
        self.selected_files_count.setFont(self.button_font)
        self.selected_files_count.setFixedSize(200, 50)

        self.move(50, 50)
        self.setFixedSize(550, 400)
        self.setWindowTitle("Konverter")
        self.setWindowIcon(QIcon(r"icon.png"))
        self.show()

    def pdf_to_docx_button_pressed(self):
        fd = QFileDialog()
        fd.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
        path = fd.getOpenFileNames(self, "PDF Dateien öffnen", home_directory, "PDF (*.pdf)")
        fd.close()
        print(len(path[0]))
        if len(path[0]) > 0:
            self.pdf_files.extend(path[0])
            print(True)
            self.update_label()

    def docx_to_pdf_button_pressed(self):
        fd = QFileDialog()
        fd.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen)
        path = fd.getOpenFileNames(self, "Docx Dateien öffnen", home_directory, "Word-Dokument ("
                                                                                                  "*.docx)")
        fd.close()
        if len(path[0]) > 0:
            self.docx_files.extend(path[0])
            self.update_label()

    def convert(self):
        directory = self.line.text()
        if not os.path.exists(directory):
            return
        if not os.path.isdir(directory):
            return
        for file in self.pdf_files:
            docx_file = name(directory + "/" + os.path.basename(file).replace(".pdf", ".docx"))
            convert_pdf_to_docx(file, docx_file)
            update_docx(docx_file)
        for file in self.docx_files:
            convert_docx_to_pdf(file, name(directory + "/" + os.path.basename(file).replace(".docx", ".pdf")))
        self.docx_files = []
        self.pdf_files = []

    def browse_button_pressed(self):
        fd = QFileDialog()
        fd.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
        path = fd.getExistingDirectory(self, "Ausgabeordner auswählen", home_directory)
        if path is not None and not len(path) <= 0:
            self.line.setText(path)

    def update_label(self):
        count = len(self.pdf_files) + len(self.docx_files)
        if count != 1:
            self.selected_files_count.setText(str(count) + " Elemente ausgewählt")
        else:
            self.selected_files_count.setText("ein Element ausgewählt")


def convert_pdf_to_docx(pdf_file, docx_file):
    cv = Converter(pdf_file)
    cv.convert(docx_file, start=0, end=None)
    cv.close()


def convert_docx_to_pdf(docx_file, pdf_file):
    convert(docx_file, pdf_file)


def update_docx(file):
    docx_format_name = 16
    file_in = os.path.abspath(file)
    file_out = file_in.replace(".docx", "2.docx")
    word_application = comtypes.client.CreateObject('word.Application')
    if word_application is not None:
        doc = word_application.Documents.Open(file_in)
        doc.SaveAs(file_out, FileFormat=docx_format_name)
        doc.Close()
        word_application.Quit()
        os.remove(file_in)
        os.rename(file_out, file_in)


app = QApplication(sys.argv)

w = MainGui()

sys.exit(app.exec())

这是我的代码。

命令是:

pyinstaller --noconfirm --onefile --windowed  "C:/Users/cpuhv/Documents/Projekte/Python/Python Test/PDFConverter.py"

答案 1 :(得分:0)

所以我昨天遇到了类似的问题。我有一种解决方法可以让程序运行,但不能作为一个文件运行。不幸的是,它还会使文件夹变大,因为它将包含您所有的虚拟环境包。我希望其他人可以为您提供更好的答案。

首先,调试时不要使用--windowed。省略 --windowed,然后使用命令行运行 .exe。这将为您显示错误。在这种情况下:

  Traceback (most recent call last):
  File "main.py", line 6, in <module>
  File "PyInstaller\loader\pyimod03_importers.py", line 531, in exec_module
  File "docx2pdf\__init__.py", line 13, in <module>
  File "importlib\metadata.py", line 551, in version
  File "importlib\metadata.py", line 524, in distribution
  File "importlib\metadata.py", line 187, in from_name
importlib.metadata.PackageNotFoundError: docx2pdf
[6860] Failed to execute script main

我们看到导入错误,因为 Pyinstaller 没有考虑二级导入。在这种情况下,我相信 docx2pdf 有自己的导入列表。查看 Hooks 和导入错误,有很多解决方案 - 但是我个人无法通过我阅读的解决方案实现结果。

因此,我为您提供的替代“bandaid”解决方案要求您使用 .spec 文件。 运行Pyinstaller --noconfirm main.py 接下来,您需要编辑在您的工作目录中生成的 main.spec 文件。 将您的 site-packages 文件夹添加到数据中,使其看起来像这样:

datas=[('C:\\PathToProjectFolder\\venv\\Lib\\site-packages', '.')]

并将 .spec 文件中的控制台行编辑为:console=False(单击 .exe 时隐藏控制台)

使用更新后的 .spec 文件,我们可以再次运行 Pyinstaller,这次输入:

pyinstaller --noconfirm main.spec

注意 main.spec,而不是 main.py。

dist 文件夹中的 .exe 现在应该可以运行了。祝你找到一个更好的解决方案,不会让你的文件夹过大。

答案 2 :(得分:0)

我为您找到了解决方案。 Onefile 和所有。我将保留我之前的答案,尽管它并不理想,但它可能对某人有所帮助。

按照此处的说明进行操作:https://github.com/AlJohri/docx2pdf/issues/5

<块引用>

pyinstaller 缺少运行 docx2pdf 的钩子脚本。

将 hook-docx2pdf.py 保存到 \Lib\site-packages\PyInstaller\hooks, 将下面的文字复制到:

<块引用>

从 PyInstaller.utils.hooks 导入 collect_all

hiddenimports = collect_all('docx2pdf')

之后我不得不退出并重新启动 PyCharm,但这修复了与 docx2pdf 导入问题相关的所有错误。

在那之后,我遇到了很多与 PyQt 相关的错误。过去我在 PyQt 和 Pyinstaller 一起工作时遇到了麻烦。

如果你从 PyQt6 降级到 PyQt5,你所有的问题都会得到解决。 然后只需将导入列表更改为从 PyQt5 导入即可。

然后运行pyinstaller --onefile -w main.py

Pyinstaller 和 PyQt6 似乎还不完全兼容。

如果这有效,如果您将其标记为已接受的答案,我将不胜感激。

答案 3 :(得分:0)

我在使用 pyinstaller 正确编译 PyQt6 时遇到了完全相同的问题。我发现问题是 pyinstaller 在创建 .exe 文件时没有复制所有需要的 PyQt6 文件。

帮助我的是使用“--add-data”标志添加整个 PyQt6 文件夹。

所以,你最终拥有:

pyinstaller --noconfirm --onefile --windowed  "C:/Users/cpuhv/Documents/Projekte/Python/Python Test/PDFConverter.py" --add-data "PYTHON_DIRECTORY/Lib/site-packages/PyQt6;PyQt6"

当然,您需要将“PYTHON_DIRECTORY”替换为 Python 目录的路径。如果您使用的是虚拟环境,请将路径放在 venv 中的 python“Lib”文件夹中。

尽管执行上述操作可能会产生重复的问题,您需要在创建的 .spec 文件中解决该问题。如果您尝试启动 Qt 应用程序并收到 the file already exists 的“QtCore.pyd, QtGui.pyd and QtWidgets.pyd”行中的错误,请不要担心。只需在您创建的 .spec 文件的“Analysis”部分后添加以下代码:

for d in a.datas:
    if 'QtCore.pyd' in d[0]:
        a.datas.remove(d)
        break

for d in a.datas:
    if 'QtGui.pyd' in d[0]:
        a.datas.remove(d)
        break

for d in a.datas:
    if 'QtWidgets.pyd' in d[0]:
        a.datas.remove(d)
        break

上面代码的作用是从“datas”中的PyQt6文件夹中删除这三个文件,因为pyinstaller创建的文件夹中已经存在这些文件。

完成此操作后,使用“pyinstaller somefile.spec”等再次运行您的构建。

如果上述解决方案也不适用于您,希望这对那里的人有所帮助