闽公网安备 35020302035485号



2. pyinstaller hello.py <other args> 根据命令行参数自动生成 spec 文件,再依据使用 spec 文件中的配置进行打包
# -*- mode: python ; coding: utf-8 -*-
# 堆代码 duidaima.com
block_cipher = None
# 这一部分负责收集你的脚本需要的所有模块和文件。的;hiddenimports 参数可以指定一些 PyInstaller 无法自动检测到的模块。
a = Analysis(
['hello.py'], # 指定要打包的 Python 脚本的路径(可以是相对路径)
pathex=[], # 用来指定模块搜索路径
binaries=[], # 包含了动态链接库或共享对象文件,会在运行之后自动更新,加入依赖的二进制文件
datas=[], # 列表,用于指定需要包含的额外文件。每个元素都是一个元组:(文件的源路径, 在打包文件中的路径)
hiddenimports=[], # 用于指定一些 PyInstaller 无法自动检测到的模块
hookspath=[], # 指定查找 PyInstaller 钩子的路径
hooksconfig={}, # 自定义 hook 配置,这是一个字典,一行注释写不下,此处先不讲
runtime_hooks=[], # 指定运行时 hook,本质是一个 Python 脚本,hook 会在你的脚本运行前运行,可用于准备环境
excludes=[], # 用于指定需要排除的模块
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# 除此之外,a 还有一些没有列出的属性:
# pure 是一个列表,包含了所有纯 Python 模块的信息,每个元素是一个元组,包含了:模块名, pyc路径, py 路径,这些模块会被打包到一个 .pyz 文件中。
# scripts 是一个列表,包含了你的 Python 脚本的信息。每个元素是一个元组,其中包含了脚本的内部名,脚本的源路径,以及一些元数据。这些脚本会被打包到一个可执行文件中。
# pyz 是指生成的可执行文件的名称。它是由 PyInstaller 用来打包 Python 程序和依赖项的主要文件。
# 创建 pyz 文件,它在运行时会被解压缩到临时目录中,然后被加载和执行。它会被打包进 exe 文件
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
# 创建 exe 文件
exe = EXE(
pyz, # 包含了所有纯 Python 模块
a.scripts, # 包含了主脚本及其依赖
[], # 所有需要打包到 exe 文件内的二进制文件
exclude_binaries=True, # 若为 True,所有的二进制文件将被排除在 exe 之外,转而被 COLLECT 函数收集
name='hello', # 生成的 exe 文件的名字。
debug=False, # 打包过程中是否打印调试信息?
bootloader_ignore_signals=False,
strip=False, # 是否移除所有的符号信息,使打包出的 exe 文件更小
upx=True, # 是否用 upx 压缩 exe 文件
console=True, # 若为 True 则在控制台窗口中运行,否则作为后台进程运行
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
# 这个对象包含了所有需要分发的文件
# 包括 EXE 函数创建的 exe 文件、所有的二进制文件、zip 文件(如果有的话)和数据文件
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello', # 生成的文件夹的名字
)
加入 Hookimport sys
from pprint import pprint
print(f'\n\n模块查找路径:')
pprint(sys.path)
print('\n')
然后,用 pyinstaller hello.spec 进行打包,再执行得到的 hello.exe,得到如下输出:

3. dist/hello/ 这个是程序所在目录
import sys
from pathlib import Path
from pprint import pprint
BASE_DIR = Path(__file__).parent
for p in sys.path.copy():
relative_p = Path(p).relative_to(BASE_DIR)
new_p = BASE_DIR / 'libs' / relative_p
sys.path.insert(0, str(new_p))
print(f'\n\n模块查找路径:')
pprint(sys.path)
print('\n')
然后,用 pyinstaller hello.spec 进行打包,再执行得到的 hello.exe,得到如下输出:

# -*- mode: python ; coding: utf-8 -*-
# 堆代码 duidaima.com
block_cipher = None
a = Analysis(
['hello.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=['hook.py'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
from pprint import pprint
pprint(a.binaries) # 打印 a.binaries
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='hello',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello',
)
然后,用 pyinstaller hello.spec 进行打包过程中得到如下输出:[('api-ms-win-crt-runtime-l1-1-0.dll',
'C:\\Portable_library\\java\\jdk-14.0.1\\bin\\api-ms-win-crt-runtime-l1-1-0.dll',
'BINARY'),
('python310.dll',
'C:\\Users\\Haujet\\AppData\\Local\\Programs\\Python\\Python310\\python310.dll',
'BINARY'),
('api-ms-win-crt-heap-l1-1-0.dll',
'C:\\Portable_library\\java\\jdk-14.0.1\\bin\\api-ms-win-crt-heap-l1-1-0.dll',
'BINARY'),
('VCRUNTIME140.dll',
'C:\\Users\\Haujet\\AppData\\Local\\Programs\\Python\\Python310\\VCRUNTIME140.dll',
'BINARY'),
# 剩下的项就省略了
]
可以看到,a.binaries 是一个列表,其中的元素是元组,元组有3个内容:# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['hello.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=['hook.py'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
import re
import os
# 用一个函数选择性对依赖文件目标路径改名
def new_dest(package: str):
if package == 'base_library.zip' or re.match(r'python\d+.dll', package):
return package
return 'libs' + os.sep + package
a.binaries = [(new_dest(x[0]), x[1], x[2]) for x in a.binaries]
# 打印 a.binaries,检查依赖文件目标路径
from pprint import pprint
pprint(a.binaries)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='hello',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello',
)
然后,用 pyinstaller hello.spec 进行打包,再执行得到的 hello.exe,得到如下输出:[('libs\\VCRUNTIME140.dll',
'C:\\Users\\Haujet\\AppData\\Local\\Programs\\Python\\Python310\\VCRUNTIME140.dll',
'BINARY'),
('python310.dll',
'C:\\Users\\Haujet\\AppData\\Local\\Programs\\Python\\Python310\\python310.dll',
'BINARY'),
('libs\\_decimal.pyd',
'C:\\Users\\Haujet\\AppData\\Local\\Programs\\Python\\Python310\\DLLs\\_decimal.pyd',
'EXTENSION'),
# 剩下的省略了
]
得到了干净的输出目录, hello.exe 也能够正常运行:
# coding: utf-8
from rich import print
def main(*args, **kwargs):
print('[red]Hello mother fucker! ')
input('按下回车继续')
if __name__ == "__main__":
main()
然后修改 hello.py,将其制作成程序入口,调用 hello_main.py 中的 main 函数:# coding: utf-8 import hello_main hello_main.main()
然后,用 pyinstaller hello.spec 进行打包,但是我们会发现,打包出的程序与之前一模一样,虽然打包出的 hello.exe 能正常运行,但是我们却找不到 hello_main.py :

# 除此之外,a 还有一些没有列出的属性: # pure 是一个列表,包含了所有纯 Python 模块的信息,这些模块会被打包到一个 .pyz 文件中。 # scripts 是一个列表,包含了你的 Python 脚本的信息。这些脚本会被打包到一个 exe 文件中。hello.py 是主脚本,会被加到 a.scripts 列表中,进而打包到 exe 中,hello_main.py 则是作为被导入的 py 模块,被加到了 a.pure 列表,后序被打包到 pyz 中。我们可以编辑 hello.spec,在打包过程中显示出有哪些 py 文件被打包了:
a = Analysis(
['hello.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=['hook.py'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
import re
import os
# 用一个函数选择性对依赖文件目标路径改名
def new_dest(package: str):
if package == 'base_library.zip' or re.match(r'python\d+.dll', package):
return package
return 'libs' + os.sep + package
a.binaries = [(new_dest(x[0]), x[1], x[2]) for x in a.binaries]
# 打印 a.pure,显示哪些 py 文件被打包
from pprint import pprint
pprint(a.pure)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
... # 后面的代码省略了
然后,用 pyinstaller hello.spec 进行打包,在输出中可以搜索到:[
...
('http.cookiejar', '...\\Python310\\lib\\http\\cookiejar.py', 'PYMODULE'),
('hello_main', 'D:\\PyInstaller优雅打包\\hello_main.py', 'PYMODULE'),
('rich', '...Python310\\lib\\site-packages\\rich\\__init__.py','PYMODULE'),
...
]
hello_main 赫然在列。# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['hello.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=['hook.py'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
import re
import os
# 用一个函数选择性对依赖文件目标路径改名,重定向到 libs 文件夹
def new_dest(package: str):
if package == 'base_library.zip' or re.match(r'python\d+.dll', package):
return package
return 'libs' + os.sep + package
a.binaries = [(new_dest(x[0]), x[1], x[2]) for x in a.binaries]
# 将需要排除的模块写到一个列表(不带 .py)
my_modules = ['hello_main', ]
# 将被排除的模块添加到 a.datas
for name in my_modules:
source_file = name + '.py'
dest_file = name + '.py'
a.datas.append((source_file, dest_file, 'DATA'))
# 筛选 a.pure
a.pure = [x for x in a.pure if x[0] not in my_modules]
# 打印 a.dates ,显示哪些文件被复制到打包文件夹
from pprint import pprint
pprint(a.datas)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='hello',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello',
)
此时,hook.py 中的 print 语句可以删掉了。[
('base_library.zip', 'D:\\PyInstaller优雅打包\\build\\hello\\base_library.zip', 'DATA'),
('hello_main.py', 'hello_main.py', 'DATA')
]
同时也可以在打包输出文件夹中看到 hello_main.py 了,并且程序能正常执行:

