目 录CONTENT

文章目录

Pyinstaller使用教程

~梓
2025-02-25 / 0 评论 / 0 点赞 / 46 阅读 / 0 字
温馨提示:
本文最后更新于2025-02-25,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1. PyInstaller 简介

pyinstaller1.webp

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 文件定制打包

  1. 生成默认 .spec 文件

    pyinstaller app.py
    
    • 生成 app.spec,用于配置打包细节。
  2. 编辑 .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  # 隐藏控制台
    )
    
  3. 通过 .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压缩

  1. 下载UPX:https://upx.github.io/

  2. 解压UPX,将路径添加到系统环境变量。

  3. 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)
0

评论区