基于Python开发一个本地项目一键启动管理工具

 更新时间:2026年06月14日 08:17:53   作者:winfredzhang  
本文主要为大家介绍了如何使用wxPython和SQLite开发一个Python文件夹一键启动工具,可以自动发现Python项目目录,生成并管理bat文件,感兴趣的小伙伴可以了解下

摘要

在日常 Python 开发中,我们经常会遇到这样的场景:电脑上散落着很多 Python 小工具、脚本项目、自动化程序,每次运行都需要进入目录、找到 .py 文件、打开命令行、手动执行命令。时间一长,项目越来越多,管理和启动都变得麻烦。

本文基于一个实际的小工具项目,详细分析如何使用:

  • wxPython 构建桌面 GUI
  • pathlib 扫描本地 Python 项目目录
  • .bat 批处理文件实现一键启动
  • sqlite3 保存文件夹元数据
  • 剪贴板截图粘贴并以 BLOB 形式存储
  • unittest 做基础功能回归测试

最终实现一个“Python 文件夹一键启动工具”:选择一个根目录后,程序会自动列出包含 .py 文件的子文件夹,支持为指定 Python 文件生成启动 bat,运行 bat,预览 bat 内容,删除 bat,并为每个文件夹维护截图、备注和作废状态。

一、项目目标

这个工具主要解决几个实际问题:

  1. 快速发现 Python 项目

    • 选择一个根目录
    • 自动扫描其中包含 .py 文件的子文件夹
  2. 为 Python 脚本生成一键启动 bat

    • 选中 .py 文件
    • 点击按钮生成 xxx_一键启动.bat
  3. 运行和管理 bat 文件

    • 选中 .bat 文件可以直接运行
    • 可以预览 bat 文件内容
    • 可以删除不再需要的 bat 文件
  4. 记录项目元信息

    • 使用 SQLite 保存文件夹编号、名称、截图、备注、是否作废等信息
  5. 支持截图粘贴

    • 从剪贴板粘贴截图
    • 在界面中显示截图缩略图
    • 保存到 SQLite BLOB 字段中

二、整体架构设计

当前项目是一个单文件桌面应用,核心文件是:

py_folder_launcher.py

整体结构可以分为三层:

┌────────────────────────────┐
│        wxPython GUI         │
│  文件夹列表 / 文件列表 / 表单 │
└─────────────┬──────────────┘
              │
┌─────────────▼──────────────┐
│       业务逻辑函数层         │
│ 扫描目录 / 生成bat / 读写截图 │
└─────────────┬──────────────┘
              │
┌─────────────▼──────────────┐
│      本地持久化层            │
│ config.json + SQLite DB     │
└────────────────────────────┘

其中:

  • config.json 用于记住上次选择的根目录
  • launcher_data.db 用于保存文件夹元数据
  • .bat 文件生成在目标 Python 文件所在目录

源码中的两个全局路径定义在 py_folder_launcher.py:15-16

CONFIG_FILE = Path(__file__).with_name("config.json")
DATABASE_FILE = Path(__file__).with_name("launcher_data.db")

这种设计的优点是部署简单:脚本、配置文件、数据库都放在同一目录,适合个人本地工具。

三、记住上次选择的文件夹

为了避免每次启动程序都重新选择根目录,程序使用 config.json 保存上次选择的路径。

相关代码位于 py_folder_launcher.py:19-33

def load_config(config_file: Path = CONFIG_FILE) -> Path | None:
    config_file = Path(config_file)
    if not config_file.exists():
        return None
    data = json.loads(config_file.read_text(encoding="utf-8"))
    selected_folder = data.get("selected_folder")
    return Path(selected_folder) if selected_folder else None


def save_config(config_file: Path, selected_folder: Path) -> None:
    config_file = Path(config_file)
    config_file.write_text(
        json.dumps({"selected_folder": str(Path(selected_folder))}, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )

这里有几个细节值得注意:

  1. 使用 Path 而不是字符串路径,路径处理更清晰。
  2. ensure_ascii=False 可以正确保存中文路径。
  3. indent=2config.json 更易读。

程序启动时会调用 load_saved_folder(),自动加载上次保存的目录,见 py_folder_launcher.py:258-264

四、扫描包含 Python 文件的文件夹

工具左侧列表展示的是“包含 .py 文件的文件夹”。

核心函数在 py_folder_launcher.py:105-111

def find_python_project_dirs(root: Path) -> list[Path]:
    root = Path(root)
    result = []
    for child in sorted((path for path in root.iterdir() if path.is_dir()), key=lambda path: path.name.lower()):
        if any(path.is_file() and path.suffix.lower() == ".py" for path in child.rglob("*.py")):
            result.append(child)
    return result

它的逻辑是:

  1. 遍历根目录下的直接子文件夹
  2. 对每个子文件夹递归查找 .py 文件
  3. 如果存在 .py 文件,就加入结果列表
  4. 按文件夹名称排序

这里使用了:

child.rglob("*.py")

这意味着即使 .py 文件在子目录的更深层级,也能识别该文件夹是一个 Python 项目目录。

五、右侧文件列表:只显示 py 和 bat

选中左侧某个文件夹后,右侧会列出该文件夹下的 .py.bat 文件。

相关函数在 py_folder_launcher.py:114-119

def list_launchable_files(folder: Path) -> list[Path]:
    folder = Path(folder)
    return sorted(
        (path for path in folder.iterdir() if path.is_file() and path.suffix.lower() in {".py", ".bat"}),
        key=lambda path: path.name.lower(),
    )

注意这里不是递归扫描,而是只显示当前文件夹下的文件。这种设计更符合“启动入口文件”的使用习惯,避免右侧列表过于复杂。

六、生成一键启动 bat 文件

生成 bat 文件是这个工具的核心功能之一。

代码位于 py_folder_launcher.py:122-141

def build_bat_content(py_file: Path) -> str:
    py_file = Path(py_file)
    return (
        "@echo off\r\n"
        "cd /d \"%~dp0\"\r\n"
        f"if not exist \"{py_file.name}\" (\r\n"
        f"    echo 找不到要启动的文件:{py_file.name}\r\n"
        "    pause\r\n"
        "    exit /b 1\r\n"
        ")\r\n"
        f"python \"{py_file.name}\"\r\n"
        "pause\r\n"
    )


def create_launcher_bat(py_file: Path) -> Path:
    py_file = Path(py_file)
    bat_file = py_file.with_name(f"{py_file.stem}_一键启动.bat")
    bat_file.write_text(build_bat_content(py_file), encoding="utf-8")
    return bat_file

生成的 bat 内容大致如下:

@echo off
cd /d "%~dp0"
if not exist "main.py" (
    echo 找不到要启动的文件:main.py
    pause
    exit /b 1
)
python "main.py"
pause

这里最关键的是:

cd /d "%~dp0"

%~dp0 表示 bat 文件自身所在目录。这样做有两个好处:

  1. 不依赖当前命令行所在目录
  2. 即使用户从其他位置双击 bat,也能正确进入脚本所在目录

另外,代码中加入了文件存在性判断:

if not exist "main.py" (...)

这可以避免出现模糊的错误信息,而是直接提示用户哪个 Python 文件不存在。

七、左侧文件夹标记:是否已生成 bat

左侧列表会显示哪些文件夹已经生成过一键启动 bat。

相关函数在 py_folder_launcher.py:144-148

def format_folder_label(folder: Path) -> str:
    folder = Path(folder)
    if any(folder.glob("*_一键启动.bat")):
        return f"[已生成] {folder.name}"
    return folder.name

设计逻辑:

如果文件夹中存在 *_一键启动.bat,显示:

[已生成] 项目A

如果没有生成 bat,只显示普通文件夹名称:

项目B

这样做比在右侧文件区域显示 [已生成] 更合理,因为右侧区域负责展示文件,左侧区域负责展示项目状态。

八、运行、预览和删除 bat

1. 运行 bat

运行 bat 的代码在 py_folder_launcher.py:162-163

def run_bat_file(bat_file: Path) -> None:
    subprocess.Popen(["cmd", "/c", "start", "", str(Path(bat_file))], shell=False)

这里使用:

cmd /c start

可以让 bat 在新的命令行窗口中运行。

2. 预览 bat 内容

预览函数在 py_folder_launcher.py:151-152

def read_bat_preview(bat_file: Path) -> str:
    return Path(bat_file).read_text(encoding="utf-8")

界面中选中 .bat 文件时,会把内容显示到只读文本框中,见 py_folder_launcher.py:407-413

3. 删除 bat

删除函数在 py_folder_launcher.py:155-159

def delete_bat_file(bat_file: Path) -> None:
    bat_file = Path(bat_file)
    if bat_file.suffix.lower() != ".bat":
        raise ValueError("只能删除 bat 文件")
    bat_file.unlink()

这里做了一个非常重要的保护:只允许删除 .bat 文件,避免误删 .py 或其他文件。

九、SQLite 保存文件夹元数据

为了给每个项目文件夹保存更多信息,程序使用 SQLite 数据库 launcher_data.db

建表代码在 py_folder_launcher.py:36-53

def ensure_database(db_path: Path = DATABASE_FILE) -> None:
    conn = sqlite3.connect(db_path)
    try:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS folder_records (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                folder_path TEXT NOT NULL UNIQUE,
                folder_name TEXT NOT NULL,
                screenshot BLOB,
                notes TEXT NOT NULL DEFAULT '',
                is_invalid INTEGER NOT NULL DEFAULT 0
            )
            """
        )
        conn.commit()
    finally:
        conn.close()

表结构如下:

字段类型说明
idINTEGER自增编号
folder_pathTEXT文件夹完整路径,唯一
folder_nameTEXT文件夹名称
screenshotBLOB界面截图二进制
notesTEXT备注信息
is_invalidINTEGER是否作废

为什么连接要显式关闭?

代码中没有使用简单的:

with sqlite3.connect(db_path) as conn:
    ...

而是手动:

conn = sqlite3.connect(db_path)
try:
    ...
finally:
    conn.close()

这是因为在 Python 的 sqlite3 中,with sqlite3.connect(...) as conn 只负责提交或回滚事务,并不会自动关闭连接。在 Windows 下,如果连接未关闭,可能导致数据库文件被锁住。

十、获取或创建文件夹记录

当用户点击左侧文件夹时,程序会自动创建或读取该文件夹对应的数据库记录。

代码在 py_folder_launcher.py:67-83

def get_or_create_folder_record(db_path: Path, folder: Path) -> dict:
    folder = Path(folder)
    ensure_database(db_path)
    conn = sqlite3.connect(db_path)
    try:
        conn.row_factory = sqlite3.Row
        row = conn.execute("SELECT * FROM folder_records WHERE folder_path = ?", (str(folder),)).fetchone()
        if row is None:
            cursor = conn.execute(
                "INSERT INTO folder_records (folder_path, folder_name) VALUES (?, ?)",
                (str(folder), folder.name),
            )
            conn.commit()
            row = conn.execute("SELECT * FROM folder_records WHERE id = ?", (cursor.lastrowid,)).fetchone()
        return row_to_record(row)
    finally:
        conn.close()

这里使用了参数化 SQL:

WHERE folder_path = ?

而不是字符串拼接,这可以避免 SQL 注入问题,也能正确处理路径中的特殊字符。

十一、保存截图、备注和作废状态

更新数据库记录的函数在 py_folder_launcher.py:86-96

def update_folder_record(db_path: Path, record_id: int, screenshot: bytes | None, notes: str, is_invalid: bool) -> None:
    ensure_database(db_path)
    conn = sqlite3.connect(db_path)
    try:
        conn.execute(
            "UPDATE folder_records SET screenshot = ?, notes = ?, is_invalid = ? WHERE id = ?",
            (screenshot, notes, int(is_invalid), record_id),
        )
        conn.commit()
    finally:
        conn.close()

界面上这三个字段是可编辑的:

  • 截图
  • 备注信息
  • 是否作废

保存按钮对应逻辑在 py_folder_launcher.py:391-405

十二、截图粘贴与显示

1. 图片格式识别

程序支持 PNG、JPEG、BMP 三类常见截图格式。

判断函数在 py_folder_launcher.py:99-102

def is_supported_image_bytes(data: bytes | None) -> bool:
    if not data:
        return False
    return data.startswith(b"\x89PNG\r\n\x1a\n") or data.startswith(b"\xff\xd8\xff") or data.startswith(b"BM")

这里通过文件头魔数判断图片格式:

格式文件头
PNG\x89PNG\r\n\x1a\n
JPEG\xff\xd8\xff
BMPBM

2. 从剪贴板粘贴截图

粘贴截图逻辑在 py_folder_launcher.py:358-382

这里有一个兼容性处理很关键。当前 wxPython 4.2.4 中没有:

wx.MemoryOutputStream
wx.MemoryInputStream

因此程序采用“临时 PNG 文件中转”的方式:

image.SaveFile(str(temp_path), wx.BITMAP_TYPE_PNG)
self.current_screenshot = temp_path.read_bytes()

这种方案虽然不是最优雅,但兼容性好,适合本地工具。

3. 显示截图缩略图

显示截图代码在 py_folder_launcher.py:329-344

核心逻辑是:

  1. SQLite 中读取 BLOB bytes
  2. 写入临时图片文件
  3. 使用 wx.Image(str(temp_path)) 读取
  4. 缩放为 240x140
  5. 设置到 wx.StaticBitmap

十三、wxPython 界面结构

主窗口类是 LauncherFrame,定义在 py_folder_launcher.py:167

界面主要分为三块:

顶部:路径显示 + 选择文件夹按钮

左侧:包含 py 文件的文件夹列表

右侧:
  - py / bat 文件列表
  - bat 内容预览
  - 文件夹信息编辑区
      - 编号
      - 文件夹名称
      - 截图显示
      - 选择截图 / 粘贴截图 / 清空截图
      - 备注信息
      - 是否作废
      - 保存信息

使用的是 wxPython 中最常见的布局方式:

wx.BoxSizer(wx.VERTICAL)
wx.BoxSizer(wx.HORIZONTAL)
wx.StaticBoxSizer(wx.VERTICAL, panel, "文件夹信息")

这类布局虽然没有前端 CSS 那么灵活,但对于桌面小工具足够实用。

十四、测试设计

项目配套了 test_py_launcher_helpers.py,使用 Python 标准库 unittest,不依赖额外测试框架。

测试覆盖了以下功能:

  1. 扫描包含 .py 文件的文件夹
  2. 列出 .py.bat 文件
  3. 生成 bat 内容
  4. 创建一键启动 bat
  5. 保存和读取 config
  6. 文件夹是否已生成 bat 的显示逻辑
  7. SQLite 字段保存
  8. 图片字节格式识别
  9. bat 内容预览
  10. 删除 bat 文件
  11. wxPython 内存流 API 兼容性回归

例如,SQLite 记录保存测试位于 test_py_launcher_helpers.py:107-123

def test_sqlite_folder_record_saves_editable_fields(self):
    with tempfile.TemporaryDirectory() as temp_dir:
        db_path = Path(temp_dir) / "launcher_data.db"
        folder = Path(temp_dir) / "project_alpha"
        screenshot = b"fake image bytes"
        folder.mkdir()

        ensure_database(db_path)
        record = get_or_create_folder_record(db_path, folder)
        update_folder_record(db_path, record["id"], screenshot, "hello", True)
        result = get_or_create_folder_record(db_path, folder)

        self.assertEqual(record["folder_name"], "project_alpha")
        self.assertEqual(record["folder_path"], str(folder))
        self.assertEqual(result["screenshot"], screenshot)
        self.assertEqual(result["notes"], "hello")
        self.assertTrue(result["is_invalid"])

wxPython 兼容性回归测试位于 test_py_launcher_helpers.py:141-145

def test_wx_stream_compatibility_does_not_use_unavailable_memory_streams(self):
    source = Path(py_folder_launcher.__file__).read_text(encoding="utf-8")

    self.assertNotIn("wx.MemoryOutputStream", source)
    self.assertNotIn("wx.MemoryInputStream", source)

这个测试是从实际问题中提炼出来的:当前 wxPython 版本没有这两个 API,因此通过测试防止后续代码再次引入不兼容写法。

十五、几个关键踩坑点

1. bat 启动目录问题

如果 bat 中直接写死绝对路径,文件移动后容易失效。

更稳妥的方式是:

cd /d "%~dp0"

这样 bat 永远从自身所在目录启动。

2. sqlite 连接不会被 with 自动关闭

很多人误以为:

with sqlite3.connect(db_path) as conn:
    ...

会自动关闭连接。

实际上它只处理事务提交和回滚,不负责关闭连接。Windows 下如果连接没关闭,可能导致数据库文件被锁住。

3. wxPython 版本 API 差异

当前环境中的 wxPython 版本是 4.2.4,不存在:

wx.MemoryOutputStream
wx.MemoryInputStream

所以图片 bytes 和 wx.Image 之间的转换采用临时文件中转方式。

4. 删除操作必须限制文件类型

删除 bat 文件时,代码明确限制:

if bat_file.suffix.lower() != ".bat":
    raise ValueError("只能删除 bat 文件")

这是一个简单但必要的安全保护。

十六、如何运行

安装依赖:

python -m pip install wxPython

运行程序:

python py_folder_launcher.py

运行测试:

python -m unittest test_py_launcher_helpers.py -v

编译检查:

python -m py_compile py_folder_launcher.py

十七、可以继续优化的方向

这个工具已经具备比较完整的本地管理能力,但还可以继续扩展:

  • 支持搜索文件夹:文件夹多时可以按名称过滤
  • 支持直接编辑 bat 内容:当前只能预览,后续可以加编辑保存
  • 截图支持双击放大:当前显示的是 240x140 缩略图
  • 支持多 Python 解释器:例如为不同项目选择不同虚拟环境
  • 支持隐藏作废项目:is_invalid 字段已经存在,可以在列表中过滤
  • 支持导出项目清单:导出为 Excel、CSV 或 Markdown
  • 把单文件拆分成模块:GUI、数据库、文件扫描、bat 管理可以拆成多个文件

十八、总结

本文分析了一个基于 wxPython + SQLite 的本地 Python 项目启动管理工具。

它的核心价值在于:

  • 自动发现 Python 项目目录
  • .py 文件生成一键启动 bat
  • 支持 bat 预览、运行和删除
  • 使用 SQLite 保存项目元数据
  • 支持截图粘贴和显示
  • 通过 unittest 保障核心逻辑稳定

这个项目虽然不复杂,但非常适合作为 Python 桌面工具开发的实战案例。它覆盖了文件系统操作、GUI 编程、批处理生成、本地数据库、剪贴板图片处理和测试回归等多个常见知识点。

对于经常维护本地 Python 小工具的人来说,这类“自用效率工具”非常有价值:不追求复杂架构,但要解决真实问题;不追求炫酷界面,但要稳定、清晰、可维护。

到此这篇关于基于Python开发一个本地项目一键启动管理工具的文章就介绍到这了,更多相关Python项目一键启动工具内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Python的Twisted框架编写非阻塞程序的代码示例

    使用Python的Twisted框架编写非阻塞程序的代码示例

    Twisted是基于异步模式的开发框架,因而利用Twisted进行非阻塞编程自然也是必会的用法,下面我们就来一起看一下使用Python的Twisted框架编写非阻塞程序的代码示例:
    2016-05-05
  • Python快速生成requirements.txt文件的5种实用命令

    Python快速生成requirements.txt文件的5种实用命令

    文章介绍了五种生成Python项目requirements.txt文件的实用命令,包括pip freeze、pipreqs、conda env export、poetry export和pip-tools,每种方法都有其适用场景和注意事项,如使用虚拟环境、定期更新依赖、处理版本冲突和确保可读性等,需要的朋友可以参考下
    2026-03-03
  • Python实现的石头剪子布代码分享

    Python实现的石头剪子布代码分享

    这篇文章主要介绍了Python实现的石头剪子布代码分享,本文和另一篇JavaScript实现的石头剪刀布游戏源码是姐妹篇,需要的朋友可以参考下
    2014-08-08
  • Python编程中实现迭代器的一些技巧小结

    Python编程中实现迭代器的一些技巧小结

    只谈迭代器的话在Python中只是一个泛指的概念,具体的可以用yield、生成器表达式、iter等多种方式来构建,这里我们整理了Python编程中实现迭代器的一些技巧小结:
    2016-06-06
  • pyinstaller还原python代码过程图解

    pyinstaller还原python代码过程图解

    这篇文章主要介绍了pyinstaller还原python代码过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Python实现运行其他程序的四种方式实例分析

    Python实现运行其他程序的四种方式实例分析

    这篇文章主要介绍了Python实现运行其他程序的四种方式,结合实例形式分析了Python执行其他程序相关模块与函数使用技巧,需要的朋友可以参考下
    2017-08-08
  • Python中Functools模块的高级操作详解

    Python中Functools模块的高级操作详解

    functools模块是Python标准库中的一个宝库,提供了一些有用的功能,可以帮助您更好地利用函数的潜力,下面小编就来为大家介绍一下functools模块的相关具体使用吧
    2023-11-11
  • Python中一些深不见底的“坑”

    Python中一些深不见底的“坑”

    这篇文章主要给大家介绍了关于Python中一些深不见底的“坑”,文中通过示例代码介绍的非常详细,对大家学习或者使用Python具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • Python进阶Matplotlib库图绘制

    Python进阶Matplotlib库图绘制

    这篇文章主要介绍了Python进阶Matplotlib库图绘制,Matplotlib:是一个Python的2D绘图库,通过Matplotlib,开发者可以仅需要几行代码,便可以生成折线图,直方图,条形图,饼状图,散点图等
    2022-07-07
  • Python的列表和元组详情

    Python的列表和元组详情

    这篇文章主要介绍了Python的列表和元组,列表和元组是python组常见的内置内省,下面文章我们讲围绕Python的列表和元组的相关资料展开话题,感兴趣的小伙伴以参考一下
    2021-10-10

最新评论