github star gitee star atomgit star PyPI Downloads AI编程 AI交流群

大家好,这里是程序员晚枫,正在all in AI编程实战

第10讲:打包分发:用PyInstaller将你的应用变成独立软件

大家好,我是程序员晚枫。在前面的课程中,我们已经开发了功能完整的AI应用。今天,我们要学习如何将Python代码打包成独立的可执行文件,让用户无需安装Python环境就能使用你的软件!

为什么需要打包分发?

在维护python-office项目的过程中,我收到了大量用户的反馈:

"晚枫,这些工具很好用,但能不能做成一个exe文件,直接双击就能运行?"
"我不想安装Python,太复杂了..."
"怎么把工具分享给不懂技术的同事?"

打包分发的价值显而易见:

  • 零依赖:用户无需安装Python或任何库
  • 易分享:一个文件就能分享所有功能
  • 专业化:从脚本升级为真正的软件
  • 保护代码:一定程度防止源码被直接查看

技术选型:PyInstaller vs Nuitka

PyInstaller - 简单易用

  • 上手简单:一行命令即可打包
  • 生态丰富:支持大多数Python库
  • 跨平台:支持Windows、macOS、Linux
  • ⚠️ 文件较大:需要捆绑Python解释器

Nuitka - 性能卓越

  • 性能更好:编译为C++,运行更快
  • 文件更小:优化后的体积更小
  • 更强保护:更好的代码保护
  • ⚠️ 配置复杂:需要更多编译配置

我的建议:初学者从PyInstaller开始,有特殊需求时再考虑Nuitka。

环境准备

1
2
3
4
5
6
# 安装打包工具
pip install pyinstaller nuitka

# 验证安装
pyinstaller --version
nuitka --version

实战:30分钟完成应用打包

基础打包:单文件应用

让我们从最简单的单文件应用开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# simple_app.py - 简单的PDF转换工具
import popdf
import os
import sys
from pathlib import Path

def main():
print("=== PDF转Word工具 ===")
print("作者: 程序员晚枫")
print("基于python-office开发")
print("-" * 30)

# 获取输入文件
if len(sys.argv) > 1:
input_file = sys.argv[1]
else:
input_file = input("请输入PDF文件路径: ").strip('"')

if not os.path.exists(input_file):
print("错误:文件不存在!")
return

# 生成输出路径
input_path = Path(input_file)
output_file = input_path.with_suffix('.docx')

print(f"输入文件: {input_file}")
print(f"输出文件: {output_file}")
print("转换中,请稍候...")

try:
# 执行转换
popdf.pdf2docx(input_file=input_file, output_file=str(output_file))
print("✅ 转换成功!")

# 打开输出目录
if sys.platform == "win32":
os.startfile(output_file.parent)
elif sys.platform == "darwin": # macOS
os.system(f'open "{output_file.parent}"')
else: # Linux
os.system(f'xdg-open "{output_file.parent}"')

except Exception as e:
print(f"❌ 转换失败: {str(e)}")

input("按回车键退出...")

if __name__ == "__main__":
main()

使用PyInstaller打包:

1
2
3
4
5
# 基础打包命令
pyinstaller --onefile --windowed simple_app.py

# 添加图标和版本信息
pyinstaller --onefile --windowed --icon=app.ico --name="PDF转换工具" simple_app.py

进阶打包:完整GUI应用

现在让我们打包之前开发的PySide6 GUI应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# build_spec.py - 高级打包配置
import PyInstaller.__main__
import os
import platform

def build_office_tools():
"""构建office工具套件"""

# 基础配置
base_options = [
'office_tools_suite.py', # 主程序文件
'--name=AI办公工具套件',
'--windowed', # 不显示命令行窗口
'--onefile', # 单文件模式
'--clean', # 清理临时文件
'--noconfirm', # 覆盖输出文件而不确认
]

# 平台特定配置
if platform.system() == 'Windows':
base_options.extend([
'--icon=assets/icon.ico',
'--version-file=version_info.txt'
])
elif platform.system() == 'Darwin': # macOS
base_options.extend([
'--icon=assets/icon.icns'
])

# 添加数据文件
data_files = [
('config/*.json', 'config'),
('templates/*.html', 'templates'),
('assets/*', 'assets')
]

for src, dest in data_files:
base_options.extend(['--add-data', f'{src}{os.pathsep}{dest}'])

# 隐藏导入(解决某些库的导入问题)
hidden_imports = [
'popdf',
'python-office',
'pypdf2',
'docx',
'openpyxl',
'python-pptx',
'PIL',
'pandas',
'numpy'
]

for imp in hidden_imports:
base_options.extend(['--hidden-import', imp])

# 排除不必要的库(减小体积)
excluded_libs = [
'matplotlib', # 如果不需要绘图功能
'scipy',
'sklearn'
]

for lib in excluded_libs:
base_options.extend(['--exclude-module', lib])

# 运行打包
PyInstaller.__main__.run(base_options)

if __name__ == "__main__":
build_office_tools()

创建版本信息文件 version_info.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# version_info.py - 自动生成版本信息
import datetime

def create_version_file():
"""创建版本信息文件"""

version_info = f"""
VSVersionInfo(
ffi=FixedFileInfo(
filevers=(1, 0, 0, 0),
prodvers=(1, 0, 0, 0),
mask=0x3f,
flags=0x0,
OS=0x40004,
fileType=0x1,
subtype=0x0,
date=(0, 0)
),
kids=[
StringFileInfo([
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'python-office'),
StringStruct(u'FileDescription', u'AI办公工具套件'),
StringStruct(u'FileVersion', u'1.0.0.0'),
StringStruct(u'InternalName', u'OfficeTools'),
StringStruct(u'LegalCopyright', u'Copyright © 2024 程序员晚枫'),
StringStruct(u'OriginalFilename', u'OfficeTools.exe'),
StringStruct(u'ProductName', u'AI办公工具套件'),
StringStruct(u'ProductVersion', u'1.0.0.0')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
]
)
"""

with open('version_info.txt', 'w', encoding='utf-8') as f:
f.write(version_info)

if __name__ == "__main__":
create_version_file()

完整的打包脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# build.py - 完整的自动化打包脚本
import os
import sys
import shutil
import subprocess
from pathlib import Path
from datetime import datetime

class ApplicationBuilder:
"""应用程序构建器"""

def __init__(self, app_name="AI办公工具套件"):
self.app_name = app_name
self.version = "1.0.0"
self.build_dir = "build"
self.dist_dir = "dist"
self.spec_dir = "spec"

# 创建必要的目录
self._create_directories()

def _create_directories(self):
"""创建必要的目录"""
directories = [self.build_dir, self.dist_dir, self.spec_dir, "assets", "config"]
for directory in directories:
Path(directory).mkdir(exist_ok=True)

def clean_build(self):
"""清理构建文件"""
print("🧹 清理构建文件...")
for directory in [self.build_dir, self.dist_dir, self.spec_dir]:
if Path(directory).exists():
shutil.rmtree(directory)
self._create_directories()

def create_assets(self):
"""创建资源文件"""
print("🎨 创建资源文件...")

# 创建默认配置文件
config_content = {
"app_name": self.app_name,
"version": self.version,
"build_date": datetime.now().isoformat(),
"settings": {
"max_file_size": 100 * 1024 * 1024,
"allowed_extensions": [".pdf", ".docx", ".xlsx", ".pptx"],
"enable_logging": True
}
}

import json
with open("config/settings.json", "w", encoding="utf-8") as f:
json.dump(config_content, f, indent=2, ensure_ascii=False)

def build_with_pyinstaller(self):
"""使用PyInstaller构建"""
print("🚀 开始PyInstaller构建...")

# PyInstaller命令参数
cmd = [
"pyinstaller",
"--name", self.app_name,
"--onefile",
"--windowed",
"--clean",
"--noconfirm",
"--distpath", self.dist_dir,
"--workpath", self.build_dir,
"--specpath", self.spec_dir,
]

# 平台特定配置
if sys.platform == "win32":
cmd.extend([
"--icon", "assets/icon.ico",
"--version-file", "version_info.txt"
])
elif sys.platform == "darwin":
cmd.extend([
"--icon", "assets/icon.icns"
])

# 添加数据文件
data_files = [
("config/*.json", "config"),
("assets/*", "assets")
]

for src, dest in data_files:
cmd.extend(["--add-data", f"{src}{os.pathsep}{dest}"])

# 隐藏导入
hidden_imports = [
"popdf", "python-office", "pypdf2", "docx", "openpyxl",
"python-pptx", "PIL", "pandas", "numpy", "pyside6"
]

for imp in hidden_imports:
cmd.extend(["--hidden-import", imp])

# 添加主程序文件
cmd.append("office_tools_suite.py")

# 执行构建命令
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print("✅ PyInstaller构建成功!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ PyInstaller构建失败: {e}")
print(f"错误输出: {e.stderr}")
return False

def build_with_nuitka(self):
"""使用Nuitka构建(可选)"""
print("⚡ 开始Nuitka构建...")

cmd = [
"nuitka",
"--standalone",
"--onefile",
"--windows-disable-console" if sys.platform == "win32" else "--macos-disable-console",
f"--output-filename={self.app_name}",
f"--output-dir={self.dist_dir}",
"office_tools_suite.py"
]

try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print("✅ Nuitka构建成功!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Nuitka构建失败: {e}")
return False

def create_installer(self):
"""创建安装程序(Windows)"""
if sys.platform != "win32":
print("ℹ️ 安装程序创建仅支持Windows平台")
return

print("📦 创建安装程序...")

# 使用Inno Setup创建安装程序
iss_content = f"""
#define MyAppName "{self.app_name}"
#define MyAppVersion "{self.version}"
#define MyAppPublisher "python-office"
#define MyAppURL "https://github.com/程序员晚枫"
#define MyAppExeName "{self.app_name}.exe"

[Setup]
AppId={{{{MyAppName}}}}
AppName={{#MyAppName}}
AppVersion={{#MyAppVersion}}
AppPublisher={{#MyAppPublisher}}
AppPublisherURL={{#MyAppURL}}
AppSupportURL={{#MyAppURL}}
AppUpdatesURL={{#MyAppURL}}
DefaultDirName={{autopf}}\\{{#MyAppName}}
DefaultGroupName={{#MyAppName}}
AllowNoIcons=yes
OutputDir=installer
OutputBaseFilename={{#MyAppName}}_Setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "chinese"; MessagesFile: "compiler:Languages\\Chinese.isl"

[Tasks]
Name: "desktopicon"; Description: "{{cm:CreateDesktopIcon}}"; GroupDescription: "{{cm:AdditionalIcons}}"; Flags: unchecked

[Files]
Source: "dist\\{{#MyAppName}}.exe"; DestDir: "{{app}}"; Flags: ignoreversion
Source: "assets\\*"; DestDir: "{{app}}\\assets"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "config\\*"; DestDir: "{{app}}\\config"; Flags: ignoreversion recursesubdirs createallsubdirs

[Icons]
Name: "{{group}}\\{{#MyAppName}}"; Filename: "{{app}}\\{{#MyAppExeName}}"
Name: "{{autodesktop}}\\{{#MyAppName}}"; Filename: "{{app}}\\{{#MyAppExeName}}"; Tasks: desktopicon

[Run]
Filename: "{{app}}\\{{#MyAppExeName}}"; Description: "{{cm:LaunchProgram,{{#StringChange(MyAppName, '&', '&&')}}}}"; Flags: nowait postinstall skipifsilent
"""

# 保存ISS文件
iss_file = "installer.iss"
with open(iss_file, "w", encoding="utf-8") as f:
f.write(iss_content)

# 执行Inno Setup编译(需要安装Inno Setup)
try:
subprocess.run(["iscc", iss_file], check=True)
print("✅ 安装程序创建成功!")
except (subprocess.CalledProcessError, FileNotFoundError):
print("ℹ️ 跳过安装程序创建(需要安装Inno Setup)")

def analyze_build(self):
"""分析构建结果"""
print("📊 分析构建结果...")

exe_path = Path(self.dist_dir) / f"{self.app_name}.exe"
if not exe_path.exists():
print("❌ 可执行文件未找到")
return

# 文件大小
file_size = exe_path.stat().st_size / (1024 * 1024) # MB
print(f"📁 文件大小: {file_size:.2f} MB")

# 依赖分析
try:
import importlib.metadata
dependencies = []
for dist in importlib.metadata.distributions():
dependencies.append(dist.metadata['Name'])

print(f"📦 包含依赖: {len(dependencies)} 个包")

except ImportError:
print("ℹ️ 依赖分析需要Python 3.8+")

def build(self, use_nuitka=False, create_installer=False):
"""执行完整构建流程"""
print(f"🏗️ 开始构建 {self.app_name}...")

# 清理环境
self.clean_build()

# 创建资源文件
self.create_assets()

# 选择构建工具
if use_nuitka:
success = self.build_with_nuitka()
else:
success = self.build_with_pyinstaller()

if success:
# 分析构建结果
self.analyze_build()

# 创建安装程序
if create_installer:
self.create_installer()

print(f"🎉 {self.app_name} 构建完成!")
print(f"📂 输出目录: {Path(self.dist_dir).absolute()}")
else:
print("❌ 构建失败,请检查错误信息")

if __name__ == "__main__":
# 创建构建器实例
builder = ApplicationBuilder("AI办公工具套件")

# 执行构建
builder.build(
use_nuitka=False, # 设置为True使用Nuitka
create_installer=True
)

解决常见打包问题

问题1:缺少依赖

创建依赖检查脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# dependency_check.py
import importlib
import sys

def check_dependencies():
"""检查所有依赖是否可用"""

required_packages = [
"popdf",
"python-office",
"pypdf2",
"docx",
"openpyxl",
"python-pptx",
"PIL",
"pandas",
"pyside6"
]

missing_packages = []

for package in required_packages:
try:
importlib.import_module(package)
print(f"✅ {package}")
except ImportError as e:
print(f"❌ {package}: {e}")
missing_packages.append(package)

if missing_packages:
print(f"\n⚠️ 缺少 {len(missing_packages)} 个依赖包:")
for package in missing_packages:
print(f" pip install {package}")
return False

print("\n🎉 所有依赖检查通过!")
return True

if __name__ == "__main__":
check_dependencies()

问题2:文件体积过大

创建优化配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# optimize_build.py
def get_optimization_options():
"""获取优化选项"""

# 排除不必要的库
exclude_modules = [
"matplotlib", "scipy", "sklearn", "torch", "tensorflow",
"jupyter", "notebook", "ipython", "ipykernel"
]

# 仅包含必要的PySide6模块
include_qt_modules = [
"QtCore", "QtGui", "QtWidgets"
]

optimization_options = {
"exclude_modules": exclude_modules,
"include_qt_modules": include_qt_modules,
"compress_level": 6, # 压缩级别
"strip": True, # 去除调试信息
"optimize": 2 # Python优化级别
}

return optimization_options

def create_optimized_spec():
"""创建优化的spec文件"""

spec_content = '''
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
['office_tools_suite.py'],
pathex=[],
binaries=[],
datas=[
('config/*.json', 'config'),
('assets/*', 'assets')
],
hiddenimports=[
'popdf', 'python-office', 'pypdf2', 'docx', 'openpyxl',
'python-pptx', 'PIL', 'pandas', 'numpy', 'pyside6'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes={exclude_modules},
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='AI办公工具套件',
debug=False,
bootloader_ignore_signals=False,
strip={strip},
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console={console},
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
'''

options = get_optimization_options()

# 替换配置变量
spec_content = spec_content.format(
exclude_modules=options["exclude_modules"],
strip=options["strip"],
console=False # 不显示控制台
)

with open("office_tools_suite.spec", "w", encoding="utf-8") as f:
f.write(spec_content)

print("✅ 优化spec文件创建完成")

if __name__ == "__main__":
create_optimized_spec()

Nuitka高级用法

对于有特殊需求的用户,可以使用Nuitka获得更好的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# nuitka_build.py
import subprocess
import sys

def build_with_nuitka_advanced():
"""使用Nuitka高级构建"""

cmd = [
"nuitka",
"--standalone",
"--onefile",
"--windows-disable-console" if sys.platform == "win32" else "--macos-disable-console",
"--include-package=popdf",
"--include-package=python-office",
"--include-package-data=pyside6",
"--output-filename=AI办公工具套件",
"--output-dir=dist",
"--remove-output",
"--assume-yes-for-downloads",
]

# 平台特定优化
if sys.platform == "win32":
cmd.extend([
"--windows-icon-from-ico=assets/icon.ico",
"--msvc=latest"
])
elif sys.platform == "darwin":
cmd.extend([
"--macos-app-icon=assets/icon.icns",
"--macos-create-app-bundle"
])

# 添加主程序
cmd.append("office_tools_suite.py")

try:
print("🚀 开始Nuitka高级构建...")
result = subprocess.run(cmd, check=True)
print("✅ Nuitka构建成功!")
except subprocess.CalledProcessError as e:
print(f"❌ Nuitka构建失败: {e}")

if __name__ == "__main__":
build_with_nuitka_advanced()

测试打包结果

创建测试脚本确保打包后的应用正常工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# test_build.py
import os
import subprocess
import tempfile
from pathlib import Path

def test_packaged_app():
"""测试打包后的应用程序"""

app_path = Path("dist") / "AI办公工具套件.exe"

if not app_path.exists():
print("❌ 打包后的应用未找到")
return False

print(f"🧪 测试应用: {app_path}")

# 创建测试文件
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as f:
f.write("这是一个测试文件,用于验证应用功能。")
test_file = f.name

try:
# 测试应用启动
result = subprocess.run(
[str(app_path), "--help"],
capture_output=True,
text=True,
timeout=30
)

if result.returncode == 0:
print("✅ 应用启动测试通过")
return True
else:
print(f"❌ 应用启动失败: {result.stderr}")
return False

except subprocess.TimeoutExpired:
print("✅ 应用启动测试通过(超时但已启动)")
return True
except Exception as e:
print(f"❌ 测试过程中出错: {e}")
return False
finally:
# 清理测试文件
if os.path.exists(test_file):
os.unlink(test_file)

def check_file_dependencies():
"""检查文件依赖"""

app_path = Path("dist") / "AI办公工具套件.exe"

if not app_path.exists():
return

# 使用依赖检查工具(如果有)
try:
import pefile
pe = pefile.PE(str(app_path))

print("📦 可执行文件信息:")
print(f" 架构: {pe.FILE_HEADER.Machine}")
print(f" 入口点: {hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint)}")

except ImportError:
print("ℹ️ 安装pefile库可以获得更详细的文件信息")

if __name__ == "__main__":
print("开始测试打包结果...")

success = test_packaged_app()
check_file_dependencies()

if success:
print("🎉 所有测试通过!")
else:
print("❌ 测试失败,请检查构建过程")

实战作业:打包你的AI应用

任务: 将你的AI办公工具打包成可执行文件

打包要求:

  1. 使用PyInstaller完成基础打包
  2. 添加应用图标和版本信息
  3. 优化文件体积
  4. 测试打包结果

打包步骤:

  1. 准备应用代码和资源文件
  2. 配置打包脚本
  3. 执行打包命令
  4. 测试打包结果
  5. 分享给朋友试用

进阶挑战:

  • 使用Nuitka进行性能优化
  • 创建Windows安装程序
  • 实现自动更新机制
  • 添加代码签名(专业版)

程序员晚枫的打包心得

在打包python-office及相关工具的过程中,我总结了几个关键经验:

  1. 渐进式打包:先确保基础功能可用,再逐步优化
  2. 充分测试:在不同环境测试打包结果
  3. 体积平衡:在功能完整性和文件大小间找到平衡
  4. 用户友好:提供清晰的安装和使用说明

记住:打包不是开发的终点,而是产品化的起点。

下一讲预告

在第11讲中,我们将学习产品化思维,探讨如何为你的软件宣传并找到第一批用户!


本节课的收获:

  • 掌握了PyInstaller的基础和高级用法
  • 了解了Nuitka作为替代方案的优势
  • 学会了解决常见的打包问题
  • 能够将Python应用转化为独立软件

课后任务:

  1. 完成你的应用打包
  2. 测试打包结果确保功能正常
  3. 分享给至少3个朋友试用
  4. 收集反馈并优化打包配置

我是程序员晚枫,我们下一讲见!


本文涉及的打包技术已在python-office多个工具中验证,稳定可靠。遇到打包问题,欢迎在课程群中交流讨论。

关于课程

我会尽我所能,把AI编程的知识分享给你。

因为对于我来说,给小白的《30讲 · AI编程训练营》是我能力范围内,最有机会抓住AI趋势的一套课。

前3讲可以试听,试听链接:https://www.bilibili.com/cheese/play/ss982042944

所以这套课的内容,只会比我承诺的更多,不会更少;只会比你预期的更用心,不会割韭菜。

同时,我也欢迎大家找我沟通,我会尽力解答你的问题。

联系作者

程序员晚枫专注AI编程培训,小白看完他和图灵社区合作的教程《30讲 · AI编程训练营》就能上手做AI项目。