如何用PyInstaller打包多文件夹大型项目?含项目结构示例
I’ve run into similar import headaches with PyInstaller before—especially when dealing with nested folder structures. The core issue here is that PyInstaller relies on static analysis to detect imports, and messy relative/absolute imports or missing package markers can cause it to miss modules. Here’s a step-by-step fix with proper import examples tailored to your structure.
First: Add __init__.py Files to Mark Packages
PyInstaller works better with explicit Python packages. Add empty __init__.py files to these folders to mark them as packages:
-repo_folder --app_folder ---__init__.py # New ---GUI_folder ----__init__.py # New ----GUI1.py ----GUI2.py ---calculations_folder ----__init__.py # New ----calculations1.py ----calculations2.py ---main.py --cli.py
Second: Fix All Imports to Be Consistent
Use either absolute imports (relative to the app package) or proper relative imports. Mixing styles will confuse PyInstaller. Here’s how each file should import:
1. cli.py (Entry Point)
Since cli.py lives outside the app package, add the repo folder to your Python path first, then import from the app package:
import sys from pathlib import Path # Add the repo folder to Python's module search path sys.path.append(str(Path(__file__).parent)) # Import the main function from app's main module from app.main import run_main if __name__ == "__main__": run_main()
2. main.py
Use relative imports (since it’s in the same parent folder as GUI_folder) or absolute imports:
# Option 1: Relative import (cleaner for same-package modules) from .GUI_folder.GUI1 import launch_gui1 # Option 2: Absolute import (explicit, works everywhere) # from app.GUI_folder.GUI1 import launch_gui1 def run_main(): print("Starting app...") launch_gui1()
3. GUI1.py
Import GUI2.py from the same subpackage:
# Relative import for same folder from .GUI2 import gui2_helper def launch_gui1(): print("Running GUI1") gui2_helper()
4. GUI2.py (Importing Calculations)
Import from the calculations_folder using absolute or relative paths:
# Absolute import (recommended for cross-package imports) from app.calculations_folder.calculations1 import calculate_sum def gui2_helper(): print("GUI2 helper running") result = calculate_sum(5, 10) print(f"Calculation result: {result}")
5. calculations1.py
Just define your functions—no special imports needed here unless you need other calculation modules:
def calculate_sum(a, b): return a + b
Third: Package with PyInstaller Correctly
Run these commands from the repo_folder directory (critical for path resolution):
Basic One-File Build
pyinstaller --onefile --name my_app cli.py
If Modules Still Go Missing
If you still get "No module named..." errors, explicitly tell PyInstaller about hidden imports using --hidden-import:
pyinstaller --onefile --name my_app \ --hidden-import app.GUI_folder.GUI2 \ --hidden-import app.calculations_folder.calculations1 \ --hidden-import app.calculations_folder.calculations2 \ cli.py
Even More Control: Use a .spec File
For complex projects, generate a spec file and edit it to list all hidden imports:
- Generate the spec file:
pyinstaller cli.py - Open
cli.specand update thehiddenimportslist in theAnalysissection:a = Analysis( ['cli.py'], pathex=[str(Path(__file__).parent)], binaries=[], datas=[], hiddenimports=[ 'app.GUI_folder.GUI1', 'app.GUI_folder.GUI2', 'app.calculations_folder.calculations1', 'app.calculations_folder.calculations2' ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, ) - Build using the spec file:
pyinstaller cli.spec
Key Tips to Avoid Future Issues
- Never use bare imports like
import GUI1—always use full package paths or relative dots. - If you use dynamic imports (e.g.,
importlib.import_module), PyInstaller can’t detect them automatically—you must add them tohiddenimports. - Always run PyInstaller from the root of your project (repo_folder) to ensure path resolution works as expected.
内容的提问来源于stack exchange,提问作者THB0705

