我有一个与 pyinstaller 捆绑在一起的应用程序。现在一个新的功能请求是,该部件使用 cyphon 编译为 c 库。
在激活的虚拟环境(诗歌)内编译后,应用程序按预期运行。
但是,当我将它与 pyinstaller 捆绑在一起时,可执行文件无法找到未在 main.py 文件中导入的包。 据我了解,这完全没问题,因为 pyinstaller 的分析阶段无法读取已编译的 c 代码的内容(在下面的示例中
modules/test/test.py
可供 pyinstaller 使用 modules/test/test.cpython-311-x86_64-linux-gnu.so
)。
├── compile_with_cython.py
├── main.py
├── main.spec
├── main_window.py
├── poetry.lock
└── pyproject.toml
import sys
from PySide6.QtWidgets import QApplication
from main_window import MainWindow
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
MVP PySide6 应用程序使用 tomllib 加载一些 toml 文件
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QVBoxLayout, QTextEdit
from PySide6.QtCore import Slot
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
...
./main
Traceback (most recent call last):
File "main.py", line 12, in <module>
File "modules/test/test.py", line 3, in init modules.test.test
ModuleNotFoundError: No module named 'tomllib'
[174092] Failed to execute script 'main' due to unhandled exception!
pyinstaller 面临的主要问题是它无法跟踪 cython 编译的文件/模块的导入。因此,它只能解析和打包
main.py
中命名的文件和库,而不能解析和打包main_window.py
中的文件和库。为了使其工作,我们需要指定 pyinstaller 隐藏的所有导入。
我找到了两种合适的解决方案,可以将 pyinstaller 与 cython 编译的二进制文件一起使用。
将任何脚本所需的任何导入添加到主 python 文件中,例如:
# imports needed by the main.py file
import argparse
import logging
import sys
import time
# dummy imports (needed by the main_window.py file)
import tomllib
import pydantic
这可行,但仅适用于小型项目。此外,指定的导入将被各种 linter 删除,因为该文件并未真正使用导入...
我在 pyinstaller 文档中找到了以下内容,为了使其正常工作,我更改了我的“.spec”文件,如下所示:
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=['tomllib', 'pydantic'],
由于上面的代码显然只是一个示例,并且我有一个包含数百个 Python 文件和库的项目,因此我想出了以下代码,以便在每次管道构建包时自动生成“hiddenimports”变量的内容:
def find_all_hidden_imports(directory_path: Path) -> set:
imports_set = set()
for file_path in directory_path.rglob('*.py'):
if ".venv" not in str(file_path):
imports_set.update(get_imports_of_file(file_path))
return imports_set
def get_imports_of_file(file_path: Path) -> set:
imports_set = set()
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
try:
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for name in node.names:
imports_set.add(name.name)
elif isinstance(node, ast.ImportFrom):
if node.module is not None:
imports_set.add(node.module)
except SyntaxError:
print(f"Syntax error in file: {file_path}")
return imports_set
然后将此集合转换为正确的列表格式字符串,然后在当前
.spec
文件中替换...