资讯详情

资讯详情

PyQt5打包+SQLite集成:天气预报Demo实战指南

简介这是一份面向PyQt5初学者与GUI开发者的实战型学习资源聚焦PyQt5桌面应用开发中的两大关键难点含图片资源的可执行文件打包与SQLite数据库集成操作。资源提供完整项目源码、分步教程笔记及可直接运行的exe示例覆盖PyInstaller多参数打包如--add-data处理图片路径、QSqlDatabase内存/文件数据库连接、数据增删查改、窗口关闭时安全断开连接等核心实践场景并延伸至天气预报API调用与JSON解析形成GUI数据网络的闭环能力训练。压缩包共32个文件含9个Python源码如MyDB.py、DataGrid.py、ZFWeather.py、2个SQLite数据库文件、2张JPG图片、1个ICO图标、1个EXE可执行程序及spec/build/dist等打包产物整体81.91MB结构清晰便于对照学习。已有444人下载学习适合希望快速掌握PyQt5工程化落地能力的开发者。1. PyQT5项目打包数据库操作一个能跑通的天气预报Demo含图片资源嵌入、SQLite连接管理、窗口关闭自动断连三件套你写完一个PyQT5天气预报界面本地运行丝滑流畅图标漂亮、图片加载正常、数据库增删改查全对——结果用PyInstaller打包成exe双击就闪退控制台一闪而过连报错都看不到或者图片全变空白数据库提示“unable to open database file”更玄学的是程序关掉后后台还挂着sqlite3连接下次启动直接报“database is locked”。这不是你代码写得差是PyInstaller对GUI资源路径的处理机制和Qt数据库连接生命周期没对齐。这个压缩包里给的不是“理论教程”而是一套已验证能落地的组合方案RunZF.py主入口 MyDB.py连接池封装 关闭窗口断开数据库连接.py显式释放逻辑 pic.py资源路径适配器 dist/RunZF.exe可执行文件实测样本。它专治三类人刚学完PyQT5控件但卡在部署环节的新人、手头有现成GUI项目却不敢交客户的中级开发者、被客户临时要求“打包发个exe我试试”的救火队员。所有源码基于Python 3.8 PyQt5 5.15.9兼容Windows 10/11不依赖外部数据库服务SQLite单文件即开即用。2. PyInstaller打包实战从命令行参数到资源路径硬编码的完整链路2.1 为什么--add-data必须分号隔开且路径方向不能反PyInstaller在Windows下使用分号;作为--add-data参数的路径分隔符前半段是源路径开发时你项目里的相对路径后半段是目标路径打包后exe解压到临时目录时的相对位置。很多人写成--add-data images;images或--add-data images\*.jpg;images结果图片全丢——因为PyInstaller不支持通配符也不识别反斜杠\在命令行中的转义歧义。正确写法必须用正斜杠/且每个资源单独加pyinstaller --onefile --windowed --icon64.ico ^ --add-data images/weather.jpg;images ^ --add-data images/one.jpg;images ^ --add-data weather_jpg.py;. ^ --add-data test.db;. ^ RunZF.py提示^是Windows CMD续行符Linux/macOS用\。--windowed禁用黑框--icon指定exe图标test.db放在根目录而非./db/下是为了让MyDB.py中os.path.join(os.path.dirname(sys.executable), test.db)能稳定定位——这是后续所有数据库操作的前提。2.2pic.py用getattr(sys, _MEIPASS, os.path.dirname(__file__))动态解析资源路径PyInstaller打包后所有资源文件图片、db、py模块会被解压到一个临时目录如C:\Users\XXX\AppData\Local\Temp\_MEIxxx此时__file__指向的是这个临时路径而非原始开发目录。硬编码./images/weather.jpg必然失败。pic.py的核心逻辑就是兜底import sys import os def resource_path(relative_path): 获取资源绝对路径兼容开发环境与PyInstaller打包环境 try: # PyInstaller创建的临时文件夹路径 base_path sys._MEIPASS except Exception: # 开发时直接用当前文件所在目录 base_path os.path.dirname(os.path.abspath(__file__)) return os.path.join(base_path, relative_path) # 使用示例 weather_img resource_path(images/weather.jpg) icon_path resource_path(64.ico)这段代码不是“玄学”而是PyInstaller官方文档明确推荐的路径适配方案。关键点在于sys._MEIPASS只在打包后存在开发时会触发Exception走except分支os.path.dirname(os.path.abspath(__file__))确保开发时路径不随工作目录变化而偏移。你在ZFWeather.py里看到的所有QPixmap(resource_path(...))调用都依赖这个函数。2.3RunZF.spec比命令行更可控的打包配置中枢当你需要频繁调整打包参数比如加更多资源、改输出目录、控制UPX压缩手动敲长命令极易出错。PyInstaller生成的.spec文件就是你的配置中心。打开RunZF.spec重点修改三处# -*- mode: python ; coding: utf-8 -*- block_cipher None a Analysis( [RunZF.py], pathex[D:/project/ZFWeather], # ← 项目根目录影响资源查找范围 binaries[], datas[ (images/weather.jpg, images), (images/one.jpg, images), (test.db, .), # ← 数据库放根目录 (64.ico, .), # ← 图标也放根目录供--icon引用 ], ... ) pyz PYZ(a.pure, a.zipped_data, cipherblock_cipher) exe EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], nameRunZF, debugFalse, bootloader_ignore_signalsFalse, stripFalse, upxTrue, # ← 启用UPX压缩需提前安装upx consoleFalse, # ← 关闭控制台窗口 disable_windowed_tracebackFalse, argv_emulationFalse, target_archNone, codesign_identityNone, entitlements_fileNone, )注意pathex必须设为你的实际项目路径否则datas里的相对路径会找不到源文件upxTrue能将exe体积从15MB压到6MB但某些杀毒软件会误报生产环境慎用consoleFalse对应--windowed避免黑框弹出。3. SQLite数据库集成从连接创建到连接释放的全生命周期管理3.1MyDB.py单例模式封装QSqlDatabase避免多连接冲突Qt的QSqlDatabase不是线程安全的且同一连接名如mydb重复addDatabase会导致未定义行为。MyDB.py用单例懒加载解决这个问题from PyQt5.QtSql import QSqlDatabase import os import sys class DatabaseManager: _instance None _db None def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) return cls._instance def get_connection(self): if self._db is None: # 动态获取db路径打包后从sys.executable同级找开发时从当前目录找 if getattr(sys, frozen, False): db_path os.path.join(os.path.dirname(sys.executable), test.db) else: db_path os.path.join(os.path.dirname(__file__), test.db) self._db QSqlDatabase.addDatabase(QSQLITE, mydb) # ← 命名连接防止冲突 self._db.setDatabaseName(db_path) if not self._db.open(): raise RuntimeError(f无法打开数据库: {self._db.lastError().text()}) return self._db def close_connection(self): if self._db and self._db.isOpen(): self._db.close() QSqlDatabase.removeDatabase(mydb) # ← 必须remove否则下次addDatabase失败 self._db None # 全局实例 db_manager DatabaseManager()这个设计的关键在于addDatabase(QSQLITE, mydb)显式命名连接避免默认名qt_sql_default_connection被其他模块覆盖removeDatabase(mydb)在关闭时必须调用否则Qt内部连接池会残留句柄getattr(sys, frozen, False)是PyInstaller打包后的标志性变量比检查sys.executable更可靠。3.2DataGrid.py用QSqlTableModel实现分页显示绕过手动SQL拼接直接写SELECT * FROM weather LIMIT 10 OFFSET 20做分页太原始。Qt提供了QSqlTableModel配合QTableView只需设置表名和过滤条件分页由视图滚动自动触发from PyQt5.QtSql import QSqlTableModel from PyQt5.QtWidgets import QTableView class WeatherModel(QSqlTableModel): def __init__(self, parentNone): super().__init__(parent) self.setTable(weather) # ← 对应数据库表名 self.setEditStrategy(QSqlTableModel.OnManualSubmit) # ← 手动提交避免误改 self.select() # ← 执行查询加载全部数据到模型 # 在主窗口中 self.model WeatherModel() self.tableView.setModel(self.model) self.tableView.resizeColumnsToContents()注意QSqlTableModel默认加载整张表大数据量时会卡顿。真实项目需结合QSqlQueryModel手写带LIMIT/OFFSET的查询但本Demo的test.db仅几十条记录QSqlTableModel足够轻量且免写SQL。3.3关闭窗口断开数据库连接.py重写closeEvent确保连接释放很多教程教你在__del__里关数据库但Python对象销毁时机不可控尤其Qt事件循环还在跑时__del__可能永远不触发。正确做法是拦截窗口关闭事件from PyQt5.QtWidgets import QMainWindow from MyDB import db_manager class MainWindow(QMainWindow): def __init__(self): super().__init__() # ... 初始化UI ... def closeEvent(self, event): 窗口关闭时强制释放数据库连接 try: db_manager.close_connection() except Exception as e: print(f[WARN] 关闭数据库连接失败: {e}) finally: event.accept() # ← 确保窗口真正关闭这段代码必须写在主窗口类里且event.accept()不能漏——否则closeEvent被拦截后窗口不会消失。这是防止“数据库被锁定”的最后一道防线。4. 避坑指南打包与数据库操作中踩过的五个真实坑4.1 现象打包后exe双击无反应任务管理器里进程一闪而逝原因缺少--windowed参数导致控制台窗口闪现后立即关闭或数据库路径错误QSqlDatabase.open()失败后未捕获异常程序直接退出。解决加--windowed参数在MyDB.py的get_connection()中用try/except包裹open()并打印错误日志到文件如log.txt方便排查临时去掉--windowed观察黑框报错内容。4.2 现象图片显示为灰色方块QLabel文字正常但Pixmap为空原因resource_path()函数里sys._MEIPASS路径拼接错误或--add-data指定的源路径不存在比如images/文件夹名大小写不符。解决在resource_path()开头加print(base_path:, base_path)确认路径是否指向_MEIxxx临时目录进入dist/RunZF/目录手动检查images/文件夹是否存在、文件名是否完全一致Windows不区分大小写但PyInstaller严格用os.listdir(base_path)打印所有文件验证资源是否真被复制过去。4.3 现象首次运行正常第二次启动报“database is locked”原因程序异常退出如CtrlC、任务管理器结束未执行closeEvent数据库连接未关闭SQLite文件被锁。解决在MyDB.py的get_connection()中打开前先尝试removeDatabase(mydb)SQLite数据库文件属性设为“只读”右键→属性→只读强制每次启动都新建连接生产环境改用PRAGMA journal_modeWAL;提升并发性需在建表后执行。4.4 现象QSqlTableModel显示空表但用DB Browser for SQLite确认表里有数据原因setTable(weather)的表名与数据库实际表名不一致如建表语句是CREATE TABLE Weather首字母大写或数据库连接未open()成功模型未select()。解决用DB Browser for SQLite打开test.db确认表名完全匹配区分大小写在WeatherModel.__init__()中加print(Table count:, self.rowCount())确认模型是否加载到数据检查QSqlDatabase.lastError()确认连接状态。4.5 现象打包后exe体积超100MB杀毒软件报毒原因PyInstaller默认打包整个Python环境包括numpy、pandas等未用模块UPX压缩被部分杀软标记为可疑。解决用--exclude-module排除不用的库--exclude-module matplotlib --exclude-module scipy改用--collect-all PyQt5替代--onefile生成目录结构再用7z压缩关闭UPX或从 UPX官网 下载最新版旧版易被误报。5. 进阶技巧用test.db做数据校验 自动初始化脚本防空库5.1database.db与test.db双库策略开发调试用test.db交付用database.db项目里同时存在两个db文件不是冗余而是分工明确test.db预置测试数据城市、温度、天气图标ID用于快速验证UI渲染逻辑database.db空库模板交付时客户可自行导入生产数据避免测试数据泄露。你在MyDB.py中看到的路径判断逻辑正是为了区分二者# 开发时优先用test.db打包后用database.db db_name database.db if getattr(sys, frozen, False) else test.db db_path os.path.join(base_path, db_name)这样既保证本地调试有数据又确保交付包干净无敏感信息。5.2DataGrid.py里藏了一个自动建表脚本防首次运行报错QSqlTableModel要求表必须存在但用户第一次运行时test.db可能是空的。DataGrid.py顶部有一段静默初始化代码from PyQt5.QtSql import QSqlQuery def init_database(): db db_manager.get_connection() query QSqlQuery(db) # 检查weather表是否存在 if not query.exec_(SELECT name FROM sqlite_master WHERE typetable AND nameweather): # 创建表 query.exec_( CREATE TABLE weather ( id INTEGER PRIMARY KEY AUTOINCREMENT, city TEXT NOT NULL, temp REAL, condition TEXT, icon_id INTEGER ) ) # 插入测试数据 query.exec_(INSERT INTO weather (city, temp, condition, icon_id) VALUES (Beijing, 25.6, Sunny, 1)) query.exec_(INSERT INTO weather (city, temp, condition, icon_id) VALUES (Shanghai, 28.3, Cloudy, 2)) # 在模块导入时自动执行 init_database()这段代码只在DataGrid.py被首次import时运行一次利用SQLite的sqlite_master元数据表判断表是否存在不存在则建表插数据。它不依赖外部脚本真正做到“开箱即用”。5.3 表格对比test.db预置数据 vsdatabase.db空库结构字段名类型是否允许NULL说明test.db示例值database.db状态idINTEGER PRIMARY KEY否自增主键1存在但无数据cityTEXT否城市名称Beijing表结构存在tempREAL是实时温度25.6表结构存在conditionTEXT是天气状况Sunny表结构存在icon_idINTEGER是图标索引1表结构存在注意database.db在交付前应手动清空test.db里的测试数据仅保留建表语句生成的空表结构。可用DB Browser for SQLite的“导出→SQL Dump”功能生成建表脚本再用sqlite3 database.db schema.sql重建。从那以后我每次交付PyQT5项目都会在dist/目录下放一个verify.bat脚本内容只有两行RunZF.exe pause双击它看exe能否正常启动、图片是否显示、表格是否有数据——这比看日志快十倍。如果闪退立刻用--console重新打包盯着黑框报错。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →