1. PyInstaller 简介
PyInstaller 是一个流行的Python打包工具,支持将Python脚本及其依赖项打包为:
- 单文件可执行程序(
--onefile
) - 多文件目录结构(默认模式)
- 支持Windows、Linux、macOS三大平台
- 自动处理第三方库依赖(如numpy、pandas等)
2. 环境准备
2.1 安装PyInstaller
pip install pyinstaller
2.2 验证安装
pyinstaller --version
# 输出示例:5.13.0
2.3 推荐使用虚拟环境
避免全局Python环境污染:
# 创建虚拟环境(以venv为例)
python -m venv venv
# 激活虚拟环境
# Windows:
myenv\Scripts\activate
# Linux/macOS:
source myenv/bin/activate
# 安装PyInstaller和项目依赖
pip install pyinstaller pandas numpy
3. 基本用法
3.1 最简单的打包命令
假设你的脚本为 app.py
:
pyinstaller app.py
- 生成的文件位于
dist/app/
目录。 - 默认生成多文件目录结构(依赖库和可执行文件分开)。
3.2 生成单文件可执行程序
pyinstaller --onefile app.py
- 生成
dist/app.exe
(Windows)或dist/app
(Linux/macOS)。 - 注意:单文件启动时会将所有依赖解压到临时目录,启动速度可能稍慢。
3.3 添加程序图标
pyinstaller --onefile --icon=app.ico app.py
.ico
(Windows)、.icns
(macOS)、.png
(Linux)格式均可。
4. 高级配置
4.1 常用命令行参数
参数 | 说明 |
---|---|
--noconsole |
隐藏命令行窗口(适用于GUI程序) |
--name |
指定输出文件名(默认与脚本同名) |
--add-data |
添加额外文件(如图片、配置文件) |
--hidden-import |
强制包含未自动检测到的模块 |
--exclude-module |
排除不需要的模块 |
--paths |
添加模块搜索路径 |
示例:
pyinstaller --onefile --name "snake" --add-data "assets/*;assets/" --hidden-import pandas app.py
4.2 使用 .spec
文件定制打包
-
生成默认
.spec
文件:pyinstaller app.py
- 生成
app.spec
,用于配置打包细节。
- 生成
-
编辑
.spec
文件:# 示例 app.spec a = Analysis( ['app.py'], pathex=['/path/project'], binaries=[], datas=[('assets/*', 'assets')], # 添加数据文件 hiddenimports=['pandas'], # 手动指定隐藏导入 hookspath=[], ... ) pyz = PYZ(a.pure, a.zipped_data) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, name='snake', icon='app.ico', console=False # 隐藏控制台 )
-
通过
.spec
文件重新打包:pyinstaller app.spec
5. 处理常见问题
5.1 依赖未正确打包
现象:运行时提示 ModuleNotFoundError
或缺失库。
解决:
-
使用
--hidden-import
明确指定缺失模块:pyinstaller --hidden-import missing_module app.py
-
检查是否在虚拟环境中安装了所有依赖。
5.2 数据文件(如图片、配置文件)未包含
解决:
-
使用
--add-data
参数:# Windows使用分号分隔路径,Linux/macOS用冒号 pyinstaller --add-data "src/images/*;images/" app.py
-
在代码中通过
sys._MEIPASS
访问打包后的资源:import sys import os def resource_path(relative_path): if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath("."), relative_path) image_path = resource_path("images/logo.png")
5.3 反病毒软件误报
现象:生成的exe文件被误判为病毒。
解决:
-
使用代码签名证书对exe签名。
-
在PyInstaller命令中添加
--key
参数加密字节码(需安装tinyaes
):pip install tinyaes pyinstaller --onefile --key=12345678 app.py
6. 优化打包体积
6.1 使用UPX压缩
-
下载UPX:https://upx.github.io/
-
解压UPX,将路径添加到系统环境变量。
-
PyInstaller自动检测UPX并压缩二进制文件。
pyinstaller --onefile --upx-dir=/path/to/upx app.py
6.2 排除无用模块
pyinstaller --exclude-module tkinter app.py
6.3 使用虚拟环境
确保虚拟环境中仅安装项目必需的依赖,避免打包无关库。
7. 跨平台打包
-
Windows打包macOS/Linux程序:不可直接跨平台,需在对应系统中执行PyInstaller。
-
使用Docker跨平台打包(示例为Linux):
docker run -v "$PWD:/src" python:3.10 bash -c "pip install pyinstaller && cd /src && pyinstaller app.py"
8. 调试技巧
8.1 查看详细打包日志
pyinstaller --log-level DEBUG app.py
8.2 测试打包后的程序
-
在命令行中运行可执行文件,观察错误输出:
./dist/app
8.3 检查生成的临时目录
- 打包后的依赖库位于
dist/app/
目录,可手动验证是否缺少文件。
9.实战演示
已知 E:\algorithm\game
目录下只有 snake.py
一个 python
文件。
import pygame
import random
import time
# 初始化Pygame
pygame.init()
# 定义颜色方案
DARK_GRAY = (30, 30, 30) # 深灰色背景
LIGHT_GRAY = (100, 100, 100) # 浅灰色网格线
SNAKE_COLOR = (76, 153, 0) # 墨绿色蛇身
FOOD_COLOR = (186, 22, 22) # 暗红色食物
TEXT_COLOR = (200, 200, 200) # 浅灰色文字
# 游戏窗口设置
WIDTH = 800
HEIGHT = 600
CELL_SIZE = 20
BASE_SPEED = 10 # 初始速度(每秒更新次数)
MAX_FPS = 60 # 最大渲染帧率
# 创建窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("贪吃蛇游戏")
# 加载中文字体
try:
font = pygame.font.SysFont('Microsoft YaHei', 24)
title_font = pygame.font.SysFont('Microsoft YaHei', 48)
except:
font = pygame.font.SysFont(None, 24)
title_font = pygame.font.SysFont(None, 48)
clock = pygame.time.Clock()
# 方向常量
UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
class Snake:
def __init__(self):
self.reset()
def reset(self):
self.length = 1
self.positions = [(WIDTH // 2, HEIGHT // 2)]
self.direction = random.choice([UP, DOWN, LEFT, RIGHT])
self.score = 0
self.speed = BASE_SPEED # 使用速度系数控制逻辑更新频率
self.last_update = 0 # 上次更新时间
def get_head_position(self):
return self.positions[0]
def move(self):
cur_head = self.get_head_position()
x, y = cur_head
if self.direction == UP:
y -= CELL_SIZE
elif self.direction == DOWN:
y += CELL_SIZE
elif self.direction == LEFT:
x -= CELL_SIZE
elif self.direction == RIGHT:
x += CELL_SIZE
new_head = (x, y)
# 碰撞检测
if x < 0 or x >= WIDTH or y < 0 or y >= HEIGHT:
return False
if new_head in self.positions[2:]:
return False
self.positions.insert(0, new_head)
if len(self.positions) > self.length:
self.positions.pop()
return True
def change_direction(self, new_dir):
if (new_dir == UP and self.direction != DOWN) or \
(new_dir == DOWN and self.direction != UP) or \
(new_dir == LEFT and self.direction != RIGHT) or \
(new_dir == RIGHT and self.direction != LEFT):
self.direction = new_dir
def draw(self, surface):
for p in self.positions:
pygame.draw.rect(surface, SNAKE_COLOR, (p[0], p[1], CELL_SIZE, CELL_SIZE))
class Food:
def __init__(self):
self.position = (0, 0)
self.randomize_position()
def randomize_position(self):
while True:
x = random.randint(0, (WIDTH - CELL_SIZE) // CELL_SIZE) * CELL_SIZE
y = random.randint(0, (HEIGHT - CELL_SIZE) // CELL_SIZE) * CELL_SIZE
self.position = (x, y)
if self.position not in snake.positions:
break
def draw(self, surface):
pygame.draw.rect(surface, FOOD_COLOR, (self.position[0], self.position[1], CELL_SIZE, CELL_SIZE))
def draw_grid(surface):
for x in range(0, WIDTH, CELL_SIZE):
pygame.draw.line(surface, LIGHT_GRAY, (x, 0), (x, HEIGHT))
for y in range(0, HEIGHT, CELL_SIZE):
pygame.draw.line(surface, LIGHT_GRAY, (0, y), (WIDTH, y))
def show_game_over():
text = title_font.render(f'游戏结束!得分: {snake.score}', True, TEXT_COLOR)
text_rect = text.get_rect(center=(WIDTH / 2, HEIGHT / 2))
restart_text = font.render('按空格键重新开始,ESC退出', True, TEXT_COLOR)
restart_rect = restart_text.get_rect(center=(WIDTH / 2, HEIGHT / 2 + 50))
screen.fill(DARK_GRAY)
screen.blit(text, text_rect)
screen.blit(restart_text, restart_rect)
pygame.display.update()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
return True
elif event.key == pygame.K_ESCAPE:
return False
# 初始化游戏对象
snake = Snake()
food = Food()
# 游戏主循环
running = True
while running:
# 时间管理
current_time = pygame.time.get_ticks()
dt = clock.tick(MAX_FPS) # 保持60FPS渲染
# 处理输入(实时响应)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
snake.change_direction(UP)
elif event.key == pygame.K_DOWN:
snake.change_direction(DOWN)
elif event.key == pygame.K_LEFT:
snake.change_direction(LEFT)
elif event.key == pygame.K_RIGHT:
snake.change_direction(RIGHT)
# 逻辑更新(固定时间间隔)
update_interval = 1000 // snake.speed # 转换为毫秒
if current_time - snake.last_update > update_interval:
if not snake.move():
if not show_game_over():
break
snake.reset()
food.randomize_position()
# 检测食物
if snake.get_head_position() == food.position:
snake.length += 1
snake.score += 10
food.randomize_position()
if snake.score % 50 == 0:
snake.speed += 1 # 速度逐渐加快
snake.last_update = current_time
# 渲染部分(始终60FPS)
screen.fill(DARK_GRAY)
draw_grid(screen)
snake.draw(screen)
food.draw(screen)
# 显示得分
score_text = font.render(f'得分: {snake.score}', True, TEXT_COLOR)
screen.blit(score_text, (10, 10))
pygame.display.update()
pygame.quit()
如果打包成功,控制台就会显示以下信息:
9.1 查看目录
打包后的目录如下

9.2 常见报错汇总
9.2.1 Pyinstaller打包报错:AttributeError: 'str' object has no attribute 'decode'
解决办法(修改源码):
去自己的python安装路径下(这里展示我的路径!):E:\algorithm\venv\Lib\site-packages\PyInstaller\compat.py
**找到406行,**修改前:
try:
if encoding:
out = out.decode(encoding)
修改后:
try:
if encoding:
out = out.encode(encoding).decode(encoding)
评论区