简介这份资源是一套基于Python的宠物信息管理系统完整项目资料面向正在学习Python开发、数据库应用或软件工程实践的初学者与进阶者可用于课程设计、毕业设计参考或自学练手。压缩包共4个文件约6MB包含sql数据库脚本、zip源码包、pptx演示文稿与docx设计文档分别对应数据建表、系统实现、答辩展示与需求设计说明。系统围绕宠物管理、主人登记、健康记录、疫苗提醒、搜索查询及报表统计等模块展开采用面向对象方式定义宠物、主人、疫苗等类并通过sqlite3或PyMySQL完成增删改查操作界面部分可借助Tkinter、PyQt等库实现。目前已有1391人学习下载读者可从中获取可运行的源码结构、数据库设计思路、功能模块划分与测试文档便于快速理解项目架构并迁移到自己的开发实践中。1. 从一张 Excel 表说起宠物信息管理系统到底要解决什么很多人第一次接触「基于 Python 的宠物信息管理系统」场景都差不多宠物医院前台有一张越拉越长的 Excel宠物名字、主人电话、疫苗时间、品种、绝育状态全塞在一起查一只猫的接种记录要 CtrlF 翻半天改错一行还容易覆盖别人的数据。这个标题要解决的就是把这张表变成一个能增删改查、能按条件筛选、数据不会互相踩踏的小型系统。它适合两类人一类是刚学完 Python 基础、想找一个真实项目练手的入门者另一类是小诊所、宠物店、救助站的兼职技术负责人想用最低成本把台账管起来。核心词「宠物信息管理系统」听着像课程设计但真做起来考的是数据建模、持久化和查询设计而不是界面花哨。下面按「先想清楚数据长什么样再动手写最后避坑」的顺序讲透。2. 先把数据模型定死宠物、主人、疫苗三张表怎么切2.1 为什么不能只用一张大表新手最容易犯的错是把所有字段堆进一张 pet 表宠物名、品种、生日、主人姓名、主人电话、疫苗名称、接种日期、下次接种……字段一多问题立刻暴露。同一只宠物打三次疫苗就要写三行宠物和主人的信息被重复三遍主人换了电话得改所有行漏一行就出现两个号码。这就是典型的没做范式拆分。合理的切法是三张表owner主人、pet宠物、vaccine疫苗记录。owner 和 pet 是一对多一个主人可以养多只宠物pet 和 vaccine 也是一对多一只宠物有多条接种记录。这样主人电话只存一份宠物信息只存一份疫苗记录通过外键挂上去。查询时用 JOIN 拼回来改数据时只改一处。选 SQLite 而不是 MySQL是因为这个量级的系统几千条记录用单文件数据库足够零配置、随项目走、备份就是复制一个 .db 文件。等数据量真上来了再迁 MySQLSQL 语句几乎不用改。2.2 建表语句与字段类型选择import sqlite3 # 连接数据库文件不存在会自动创建 conn sqlite3.connect(pet_system.db) cursor conn.cursor() # 主人表一个主人对应多个宠物 cursor.execute( CREATE TABLE IF NOT EXISTS owner ( owner_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT NOT NULL UNIQUE, -- 电话唯一防止重复录入 address TEXT ) ) # 宠物表通过 owner_id 外键关联主人 cursor.execute( CREATE TABLE IF NOT EXISTS pet ( pet_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, species TEXT NOT NULL, -- 猫/狗/兔等 breed TEXT, birth_date TEXT, -- 存 ISO 格式 YYYY-MM-DD gender TEXT CHECK(gender IN (公,母,未知)), owner_id INTEGER NOT NULL, FOREIGN KEY (owner_id) REFERENCES owner(owner_id) ) ) # 疫苗表通过 pet_id 外键关联宠物 cursor.execute( CREATE TABLE IF NOT EXISTS vaccine ( vac_id INTEGER PRIMARY KEY AUTOINCREMENT, pet_id INTEGER NOT NULL, vac_name TEXT NOT NULL, vac_date TEXT NOT NULL, next_date TEXT, -- 下次接种提醒日期 FOREIGN KEY (pet_id) REFERENCES pet(pet_id) ) ) conn.commit()这段代码的逻辑是先建主人表再建宠物表并声明外键指向主人最后建疫苗表指向宠物。参数上AUTOINCREMENT让主键自增不用手动维护 IDUNIQUE加在 phone 上从数据库层面挡住重复主人CHECK约束把性别限定在三个值避免有人填「男」「female」这种脏数据。日期统一用 TEXT 存 ISO 格式是因为 SQLite 没有原生日期类型ISO 字符串可以直接用字符串比较做范围查询比如WHERE vac_date 2025-01-01排序也不会乱。提示外键约束在 SQLite 里默认是关闭的每次连接后要执行cursor.execute(PRAGMA foreign_keys ON)否则删主人时不会拦住还挂着的宠物。2.3 用 Python 定义函数封装增删改查热词里「python定义函数」出现频率很高这里正好用上。把每个操作封成函数主流程只调用函数代码才管得住。def add_owner(name, phone, address): 新增主人电话重复时抛异常 try: cursor.execute( INSERT INTO owner (name, phone, address) VALUES (?, ?, ?), (name, phone, address) ) conn.commit() return cursor.lastrowid # 返回新主人的 ID except sqlite3.IntegrityError: print(f电话 {phone} 已存在请勿重复录入) return None def add_pet(name, species, owner_id, breed, birth_date, gender未知): 新增宠物owner_id 必须已存在 cursor.execute( INSERT INTO pet (name, species, breed, birth_date, gender, owner_id) VALUES (?, ?, ?, ?, ?, ?), (name, species, breed, birth_date, gender, owner_id) ) conn.commit() return cursor.lastrowid def find_pets_by_owner(phone): 按主人电话查他名下所有宠物 cursor.execute( SELECT p.pet_id, p.name, p.species, p.breed FROM pet p JOIN owner o ON p.owner_id o.owner_id WHERE o.phone ? , (phone,)) return cursor.fetchall()三个函数的职责很清楚add_owner负责写主人并处理电话冲突add_pet负责写宠物find_pets_by_owner负责联表查询。参数全部用?占位符传这是防 SQL 注入的关键——千万不要用 f-string 把用户输入拼进 SQL否则主人名字里带个引号就能把语句搞崩。cursor.lastrowid拿到刚插入行的自增 ID方便紧接着给这只宠物加疫苗记录。3. 查询与筛选把「找一只猫」做成能用的功能3.1 多条件组合查询的写法真实使用中前台不会只按一个条件查。常见组合是「品种是猫 主人电话尾号 最近三个月打过疫苗」。硬写死 SQL 不现实得动态拼条件。def search_pets(speciesNone, keywordNone, vaccinated_afterNone): 按品种、关键字宠物名或主人名、接种时间组合筛选 sql SELECT p.name, p.species, p.breed, o.name AS owner_name, o.phone FROM pet p JOIN owner o ON p.owner_id o.owner_id WHERE 11 params [] if species: sql AND p.species ? params.append(species) if keyword: sql AND (p.name LIKE ? OR o.name LIKE ?) params.append(f%{keyword}%) params.append(f%{keyword}%) if vaccinated_after: sql AND p.pet_id IN ( SELECT pet_id FROM vaccine WHERE vac_date ? ) params.append(vaccinated_after) cursor.execute(sql, params) return cursor.fetchall()这里用WHERE 11起头是为了后面每个条件都能无脑接AND不用判断是不是第一个条件。LIKE配合%关键字%做模糊匹配宠物名和主人名任一命中都算。接种时间用子查询IN过滤比 JOIN 再 DISTINCT 更直观。参数列表和 SQL 里的?顺序必须严格对应这是最容易翻车的地方——加了一个条件忘了 append 参数运行时就报参数数量不匹配。3.2 用 pandas 做统计报表热词里「python dataframe」「python写入excel」指向一个真实需求月底要出一张「各品种宠物数量」「本月新增接种数」的报表。查询结果直接丢给 pandas几行就能出表。import pandas as pd def export_species_report(outputspecies_report.xlsx): 统计各品种数量并导出 Excel df pd.read_sql_query( SELECT species AS 品种, COUNT(*) AS 数量 FROM pet GROUP BY species ORDER BY 数量 DESC , conn) # 追加一行合计 total pd.DataFrame([{品种: 合计, 数量: df[数量].sum()}]) df pd.concat([df, total], ignore_indexTrue) df.to_excel(output, indexFalse) print(f报表已生成{output}) return dfpd.read_sql_query直接把 SQL 结果转成 DataFrame列名用 SQL 里的别名导出时表头就是中文。GROUP BY species做分组计数ORDER BY 数量 DESC让多的排前面。to_excel需要 openpyxl 支持没装的话先pip install openpyxl。indexFalse是为了不把 pandas 的行号写进 Excel 第一列否则表头会错位。注意read_sql_query传的是连接对象 conn如果前面 commit 后连接被关过这里会报错保持连接常开或用上下文管理器重新打开。3.3 疫苗到期提醒怎么算系统真正有价值的地方是能主动告诉你「哪只宠物该打疫苗了」。核心是拿next_date和今天比。from datetime import date, timedelta def get_due_vaccines(days_ahead7): 查出未来 N 天内需要接种的宠物 today date.today().isoformat() deadline (date.today() timedelta(daysdays_ahead)).isoformat() cursor.execute( SELECT p.name, o.name, o.phone, v.vac_name, v.next_date FROM vaccine v JOIN pet p ON v.pet_id p.pet_id JOIN owner o ON p.owner_id o.owner_id WHERE v.next_date BETWEEN ? AND ? ORDER BY v.next_date , (today, deadline)) return cursor.fetchall()date.today().isoformat()得到2025-06-01这种字符串和数据库里存的格式一致才能正确比较。timedelta(days7)算出七天后的日期BETWEEN取闭区间。返回结果按next_date升序最急的排最前。这个函数可以挂到每天定时任务里把结果发到前台。4. 避坑与排查那些让系统当场翻车的细节4.1 中文乱码现象是存进去变问号现象主人名字「张伟」存进数据库查出来变成??或乱码。原因通常是连接时没指定编码或者终端本身编码不是 UTF-8。解决SQLite 默认就是 UTF-8问题多出在读写文件或打印环节。Python 3 的字符串本身是 Unicode写库不会乱但如果用open()读 CSV 导入要显式写encodingutf-8。Windows 命令行打印中文乱码是终端代码页问题chcp 65001切到 UTF-8 即可跟数据库无关。4.2 外键不生效删了主人宠物还在现象删掉一个主人他名下的宠物记录还挂在库里成了孤儿数据。原因SQLite 外键约束默认关闭建表时写了FOREIGN KEY也没用。解决每次建立连接后立刻执行PRAGMA foreign_keys ON并且建表时给外键加上ON DELETE CASCADE这样删主人时宠物自动跟着删。但要注意级联删除是双刃剑删之前最好先查一遍名下有几只宠物让用户确认。4.3 日期比较失效字符串格式不统一现象WHERE vac_date 2025-1-1查不到 2025 年 1 月的数据。原因数据库里存的是2025-01-01补零查询条件写的是2025-1-1没补零字符串比较逐字符比01和1第一位就不同。解决所有日期入库前统一用isoformat()或strftime(%Y-%m-%d)格式化查询条件也走同一个函数绝不手写日期字符串。4.4 参数数量不匹配动态 SQL 的经典翻车现象调用search_pets时报Incorrect number of bindings supplied。原因SQL 里加了新条件?但忘了往 params 里 append 对应值或者 append 顺序和?出现顺序不一致。解决养成习惯每写一个?就紧跟一行 append条件块和参数块挨着写。调试时先把拼好的 sql 和 params 打印出来肉眼对一遍再执行。4.5 并发写入锁库多个人同时点保存现象两个人同时录入其中一个报database is locked。原因SQLite 同一时刻只允许一个写操作另一个会被阻塞超时后抛错。解决小团队场景下给连接设置timeoutsqlite3.connect(pet_system.db, timeout10)让第二个写等 10 秒而不是立刻失败。如果并发真的高就该换 PostgreSQL 或 MySQL 了SQLite 不适合多写场景。5. 进阶把系统从「能跑」推到「敢用」前面四章把功能跑通了但离「敢交给前台天天用」还差几步。第一是加一层输入校验别指望用户自觉。宠物生日字段用户可能填「去年」「2025.1.1」「20250101」全得在入库前用datetime.strptime试解析解析失败就提示重填而不是把脏数据塞进库。第二是给关键操作加事务比如「新增宠物 同时加一条疫苗记录」应该是一个原子操作中途失败要整体回滚否则会出现有宠物没疫苗的半截数据。def add_pet_with_vaccine(pet_info, vac_info): 宠物和首针疫苗一起写入任一失败则回滚 try: cursor.execute(BEGIN) pet_id add_pet(**pet_info) cursor.execute( INSERT INTO vaccine (pet_id, vac_name, vac_date, next_date) VALUES (?, ?, ?, ?), (pet_id, vac_info[name], vac_info[date], vac_info[next]) ) conn.commit() return pet_id except Exception as e: conn.rollback() print(f写入失败已回滚{e}) return NoneBEGIN显式开启事务两步都成功才commit任何一步抛异常就rollback数据库回到操作前的状态。这是「后悔药」没有它一次失败就可能留下查不到主人的孤儿宠物。第三是备份习惯。SQLite 的备份简单到可以写进定时任务sqlite3 pet_system.db .backup backup_$(date %F).db每天一份出问题直接换文件。别小看这一步我见过太多人系统写得漂亮硬盘一坏全没了。验证系统是否真的可用我一般做三件事一是造 500 条假数据压一遍查询看响应是否还在毫秒级二是故意输错数据看校验拦不拦得住三是模拟两个人同时录入看锁库报错有没有被 timeout 兜住。这三关过了才敢让前台用。最后说个我自己的教训早期我图省事把主人电话当主键用结果一个主人换号所有关联全断。后来老老实实加自增 ID 做代理键业务字段只做唯一约束再没出过这类问题。做管理系统主键永远用无意义的自增 ID别拿业务字段当主键这条习惯帮我省了无数次返工。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?