Python期末作业智能停车管理系统:从需求拆解到JSON持久化与flet界面完整指南
2026/9/23 14:33:05 网站建设 项目流程

简介:一份用Python编写的智能停车管理系统模拟出入场项目,面向Python期末作业、课程设计或K12阶段进阶练习,可完整演示车辆入场、出场计费到信息持久化的过程。系统采用OpenCV实现车牌识别,结合YOLO+OCR深度学习方案,利用MySQL完成停车信息存储与计费查询,并通过pygame搭建用户交互界面;在业务流程上涵盖入场识别、出场计费、车位信息更新、数据库写入等环节,同时完成可行性分析、需求分析、概要设计、详细设计到软件测试的完整开发流程。压缩包共十二个文件,包含八个Python脚本、三个keep占位文件及一个说明txt,大小仅17KB,脚本按数据库、车牌识别、计费、主程序等模块拆分,便于阅读与复用,适合在课程设计中参考。目前已有762人学习浏览,说明该方案具备一定参考价值。读者可从中获取完整代码框架、GUI设计思路及计费算法实现,并可根据项目文档快速梳理E-R模型与模块职责,直接作为期末作业模板或二次开发基础。

1. Python期末作业做智能停车管理系统,为什么我劝你别一上来就写代码

期末作业里“智能停车管理系统(模拟出入场)”算是个高频题目,Word 版需求翻来覆去就是那几句:能模拟车辆入场、出场,显示剩余车位,按停车时长计费。听起来不难,但每年都有人在这个题目上翻车——不是逻辑写不出来,而是把项目做成了一个“一次性脚本”:运行、输入、出结果、关掉,数据全丢,代码也只有一百行,答辩时老师问“你用了什么数据结构”“数据怎么持久化的”,直接卡壳。这个题目的本质不在于“停车”业务有多复杂,而在于用 Python 把一套完整的小型业务流程走通:状态管理、时间计算、计费规则、数据保存、交互界面。它适合 Python 基础语法学完、想用面向对象和文件操作串一个综合项目的初学者,也是后续做 Web 版或图形界面版停车系统的地基。本篇就按我实际带过的课程设计思路,把这条路从头拆到尾,顺便把那些让人折腾到半夜的坑提前指出来。

2. 先把需求拆干净:这个系统到底在模拟什么

拿到题目别急着开 IDE。先花半小时把“模拟出入场”五个字翻译成程序里的功能点,这一步决定了后面类的设计、方法划分和数据存法。很多人的期末项目之所以代码越写越乱,就是因为在需求还没理清的时候就开始写输入输出。

2.1 从一句话需求到功能清单

“模拟出入场”至少包含五件事:车位有总数和剩余数,车辆入场要登记并占用一个车位,车辆出场要释放车位,按停车时长收费,中间任何时刻能查询当前车位占用情况。把这几件事再细化成可验收的交互动作,就得到下面这张功能清单。

模块功能点对应期末考核点
入场登记输入车牌,记录入场时间,剩余车位减一输入处理、时间获取、状态更新
出场结算输入车牌,查找入场记录,计算时长并计费,剩余车位加一字典查找、时间计算、分支判断
车位查询显示总车位、剩余车位、当前在场车辆列表数据遍历、格式化输出
计费规则按白天/夜间不同单价或统一单价计算函数封装、参数设计
数据持久化程序重启后仍能恢复停车记录文件读写、JSON 序列化

注意这里有一个容易被忽略的点:查询功能是“随时可查”,这意味着系统里必须有一份“在场车辆”的实时状态,而不是等到出场时才去翻历史文件。很多初版代码把记录存在本地列表里,跑一次程序没问题,重启后查不到任何在场车辆,根因就是把“内存状态”和“持久化数据”混为一谈了。

2.2 用类还是用字典:期末答辩视角下的选择

用纯字典写这个系统完全跑得通,比如cars = {"京A12345": {"入场时间": 1699000000}},但期末项目讲究的是“展示学习成果”。用类封装能让代码结构更清晰,也自然覆盖了“面向对象”这个考点。我一般建议把系统拆成两个核心类:一个描述车辆,一个描述停车场。

  • Car类:记录车牌和入场时间戳,也可以预留一个备注字段,方便后续扩展会员标记。
  • ParkingLot类:管理总车位数、在场车辆字典、计费单价等,提供入场、出场、查询、保存、加载方法。

对比一下两种方案:纯字典实现起来代码量少,但出场结算、数据校验、格式统一这些逻辑全散落在函数里;用类实现,每个职责落进方法内部,答辩时老师问“车辆数据存在哪”,你直接指Car类的字段就能说清楚。从课程设计的角度,类方案不仅更好写,也更好讲。

2.3 先搭骨架:ParkingLot 类的字段与初始化逻辑

类的字段设计决定了后续所有方法的复杂度。我的建议是字段宁多勿缺,但不要提前做“可能未来会用到”的过度设计。下面这个骨架足够应付出入场、计费和持久化三件事。

import json import time from typing import Dict, Optional class Car: """车辆实体类,保存一辆车在停车场内的关键信息""" def __init__(self, plate: str, enter_time: float): self.plate = plate # 车牌号,统一大写,去除首尾空格 self.enter_time = enter_time # 入场时间戳,float 类型 class ParkingLot: def __init__(self, total_spaces: int, day_price: float, night_price: float): self.total_spaces = total_spaces # 总车位数 self.cars: Dict[str, Car] = {} # 在场车辆字典,key 为车牌 self.day_price = day_price # 白天单价(元/小时) self.night_price = night_price # 夜间单价(元/小时) @property def available_spaces(self) -> int: """剩余车位:总数减去在场车辆数""" return self.total_spaces - len(self.cars)

这段代码有几个值得在答辩时讲的细节。cars字典用plate做 key,出场时直接self.cars.get(plate)就能拿到记录,时间复杂度是 O(1),比遍历列表快得多。available_spaces@property装饰成属性,调用时不用加括号,语义上更像“一个状态”而不是“一个动作”。车牌统一在入口处做格式化,后面所有查询、比对都不会因为大小写或首尾空格翻车。

3. 出入场核心逻辑:从入场登记到离场计费

骨架搭好后,核心就是入场和出场这两个方法。这两个方法写好了,系统的主流程就通了。注意计费部分不要和业务逻辑混在一起,单独抽一个方法出来,方便改价格规则。

3.1 入场流程:先查容量,再登记车辆

入场方法要做三件事:检查是否还有空车位,检查车牌是否已经入场,通过校验后写入cars字典。这三步顺序不能乱——如果先把车加进去再检查容量,满位时就会出现负数空位。

def enter(self, plate: str) -> bool: """车辆入场登记,成功返回 True,失败返回 False""" plate = plate.strip().upper() # 去空格、转大写,统一格式 if not plate: print("车牌号不能为空") return False if len(self.cars) >= self.total_spaces: print("停车场已满,无法入场") return False if plate in self.cars: print(f"车牌 {plate} 已在停车场内,请勿重复入场") return False self.cars[plate] = Car(plate, time.time()) # time.time() 返回当前时间戳 print(f"车辆 {plate} 入场成功,剩余车位 {self.available_spaces}") return True

time.time()返回的是自 1970 年 1 月 1 日以来的秒数,也就是 Unix 时间戳。有人喜欢用datetime.now()存字符串,但时间戳的优点是:计算时长直接用减法,格式化展示时再转换,存储和计算分离。对期末作业来说,这个习惯能帮你在出场计费时少掉一半的字符串解析代码。重复入场检查看起来多余,但实际操作中手误重复输入同一车牌非常常见,这个判断能避免“同一辆车占了两个车位”的尴尬局面。

3.2 出场流程:查找记录、计算时长、按规则计费

出场方法比入场多两个步骤:按车牌找到入场记录,计算停车时长并计费。最关键的边界是“查不到记录”——如果用户输入了一个没入场的车牌,程序必须给出提示而不是抛异常。

def leave(self, plate: str) -> Optional[float]: """车辆出场结算,返回停车费用,若车辆不在场返回 None""" plate = plate.strip().upper() car = self.cars.get(plate) if car is None: print(f"未找到车牌 {plate} 的入场记录") return None stay_seconds = time.time() - car.enter_time stay_hours = stay_seconds / 3600 fee = self.calc_fee(stay_hours) del self.cars[plate] print(f"车辆 {plate} 停车 {stay_hours:.2f} 小时,费用 {fee:.2f} 元") print(f"出场成功,剩余车位 {self.available_spaces}") return fee

self.cars.get(plate).get()而不是self.cars[plate],区别在于前者在 key 不存在时返回None,后者直接抛KeyError。对用户输入类程序,永远优先用.get()stay_hours是浮点数,保留两位小数展示,但参与计费时用原始精度,避免多次四舍五入把账算错。del self.cars[plate]放在所有计算之后,保证即使计费函数报错,车辆记录也不会被误删。

3.3 计费参数怎么设:白天夜间双单价与最小计费单元

计费规则是评分的重头戏,也是最能体现“可配置性”的地方。我见过的大部分参考实现都是单一价格乘时长,但稍微升级一点的做法是把一天拆成白天和夜间两段,价格不同。这里的关键不是规则多复杂,而是参数要暴露出来,不能写死在计算逻辑里。

def calc_fee(self, stay_hours: float) -> float: """计费逻辑:白天按 day_price,夜间按 night_price,不足一小时按一小时计""" if stay_hours <= 0: return 0.0 whole_hours = math.ceil(stay_hours) # 向上取整,不足 1 小时按 1 小时 fee = 0.0 for i in range(whole_hours): hour_time = (self.car_enter_hour + i) % 24 # 需要传入入场小时数 if 8 <= hour_time < 20: fee += self.day_price else: fee += self.night_price return fee

这个版本的计费逻辑有一个隐藏问题:self.car_enter_hour没有定义,需要把入场时刻的小时数传进来。常见的修正方案是修改函数签名为calc_fee(self, stay_hours: float, enter_hour: int),然后按“每整小时判断一次该小时属于白天还是夜间”来累加。比如入场是 19:30,停了 2.5 小时,系统会按 3 小时算,其中 19:30-20:00 这不足一小时的 30 分钟按夜间算,还是按小时单价算,不同老师对规则的理解可能不同。我的建议是:在代码注释里写明“费用按整小时向上取整,不足一小时按一小时计”,老师问起时你能答得有理有据,比规则本身更重要。

4. 把数据存下来:JSON 持久化与最小图形界面

做完出入场逻辑,系统已经能跑,但关掉程序再打开,所有在场车辆全没了。这样交上去,功能上说得通,工程上站不住脚。接下来这一步是把“运行期数据”变成“文件里的数据”,再决定要不要加个界面。

4.1 数据存 JSON 还是 txt:对比与选择

Python 写文件有两种常见选择:json.dump序列化字典,或者按固定分隔符写文本。对课程设计这个规模,JSON 几乎是唯一合理的答案——结构清晰、自带层级关系、json.load直接还原成字典,省去自己写解析逻辑的麻烦。用 CSV 存储也行,但 csv 模块的读写需要处理表头和字段顺序,代码量明显增加。

我一般建议这样做:每次入场、出场操作成功后,立即把当前cars字典写到 JSON 文件。不要等程序退出时统一保存,因为如果程序在退出前崩溃,数据还是会丢。

def save_to_file(self, filename: str = "parking_data.json") -> None: """把当前在场车辆数据保存到 JSON 文件""" data = { "total_spaces": self.total_spaces, "cars": { plate: car.enter_time for plate, car in self.cars.items() } } with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2)

这里有个细节:ensure_ascii=False保证中文车牌存储后以明文形式出现在 JSON 里,而不是转成\u编码,后面调试时直接用文本编辑器打开就能看到内容。indent=2把 JSON 排成带缩进的格式,方便肉眼检查。with open语句确保文件写入完成后自动关闭,异常时也不会残留文件句柄。

加载文件的方法逻辑刚好相反,注意处理文件不存在的情况。第一次运行程序时文件还没生成,直接open会抛FileNotFoundError,所以要先用os.path.exists判断。

4.2 环境准备:pycharm 或 vscode 配置 python 环境与第三方库安装

如果是纯控制台版,只需要 Python 标准库,不需要额外安装任何第三方库。但如果你想把界面做成图形版本,常见选择是flet——它基于 Flutter 的渲染引擎,用纯 Python 写界面,且能直接打包成桌面应用,对期末项目来说投入产出比很高。这里先说清楚环境配置,避免卡在第一步。

在 pycharm 里新建项目时选择已存在的解释器,或者在设置里找到 Python Interpreter 添加,这是最常用的方式。如果用 vscode,需要先装 Python 扩展,再用python -m pip install flet安装第三方库。注意用python -m pip而不是直接pip,因为后者可能会装到系统的 Python 而不是当前激活的虚拟环境中。

python -m pip install flet

装完后在 Python 交互环境里执行import flet,不报错就说明环境就绪。flet 的版本更新比较快,不同版本的部分参数名有调整,如果发现示例代码报错,优先去查你当前版本的 API 变化。

4.3 用 flet 搭一个最小可交互界面

控制台版的逻辑已经完整,图形界面版的关键是把之前写好的enterleave、查询三个方法接到按钮事件上。下面这个最小示例展示了 flet 的基本用法:页面由文本框、按钮和列表组成,点击按钮时调用ParkingLot的方法并刷新界面。

import flet as ft from parking_lot import ParkingLot lot = ParkingLot(total_spaces=50, day_price=5.0, night_price=2.0) def main(page: ft.Page): page.title = "智能停车管理系统" plate_input = ft.TextField(label="车牌号") info_text = ft.Text("剩余车位:50") def on_enter(e): plate = plate_input.value result = lot.enter(plate) info_text.value = f"剩余车位:{lot.available_spaces},最近操作:{result}" page.update() def on_leave(e): plate = plate_input.value fee = lot.leave(plate) if fee is not None: info_text.value = f"剩余车位:{lot.available_spaces},本次收费:{fee:.2f} 元" page.update() page.add( plate_input, ft.Row([ ft.ElevatedButton("入场", on_click=on_enter), ft.ElevatedButton("出场", on_click=on_leave), ]), info_text ) ft.app(target=main)

ft.TextFieldvalue是用户输入的内容,点击按钮后通过事件函数读取并交给业务逻辑处理。页面组件需要用page.update()手动刷新,让界面上显示最新数据。这个版本把“业务逻辑”和“界面显示”分开:ParkingLot里的方法只负责数据处理,界面上只管调用和展示。答辩时老师如果问“界面和逻辑怎么解耦”,你可以直接指着这两个文件说明。不想用图形界面时,控制台版用input()也能完成同样功能,直接在主文件里写一个while True循环,根据用户输入的分支调用对应方法即可。

5. 避坑:期末项目最常翻车的 6 个细节

做课程设计最怕的不是功能写不出来,而是写完了却在演示、答辩时翻车。下面这些坑我几乎每年都见到学生踩一遍,提前排掉能省下大量改代码的时间。

5.1 车牌大小写不一致导致“找不到入场记录”

现象:入场时输入京A12345,出场时输入京a12345,系统提示找不到记录。原因:用户输入的车牌大小写不一致,而字典的 key 是严格区分大小写的。解决方法:在enterleave方法中统一调用plate.strip().upper(),强制把车牌转成大写再存储和查找。这一步要放在所有逻辑之前,而不是只在某一个方法里做,一旦某个入口漏了,演示时就会当场翻车。

5.2 程序退出后数据全丢

现象:程序正常运行,所有操作都成功,但关闭后重新打开,剩余车位恢复满员,在场车辆列表为空。原因:数据只存在内存里的cars字典中,没有写盘。解决方法:把save_to_file方法在enterleave各成功分支后各调用一次。不要用“退出时统一保存”的方案,因为exit()之前的代码可能被异常打断。用 try/finally 包住主循环也行,但最稳的做法是“每次变更立即保存”。

5.3 计费出现负数

现象:停车 2 小时,费用显示为负。原因:系统当前时间小于入场记录的时间戳,或者手动修改了系统时间导致时差异常。解决方法:在calc_fee开头加一行stay_hours = max(0, stay_hours),把负值钳制为零。千万别在和老师演示的时候手动调系统时间玩“穿越停车”,这个坑是自己给自己挖的。

5.4 满位判断与车辆登记顺序写反

现象:车位只剩 1 个时,连续入场 2 辆车,第二辆也显示成功。原因:代码先执行了self.cars[plate] = Car(...),再判断len(self.cars) >= self.total_spaces,导致满位判断永远晚一步。解决方法:严格按“先判断、后写入”的顺序执行。这类逻辑错误的特征是极难在功能测试中暴露,只有并发或连续操作时才会显现,但期末项目里数据量小,思路清晰比压测更重要。

5.5 文件加载后类属性缺失

现象:写好了load_from_file方法,但加载后调用available_spaces报错。原因:加载方法只还原了cars字典,没有还原total_spaces和单价属性,对象处于“半初始化”状态。解决方法:加载文件后执行一次完整的初始化流程,可以写一个类方法直接从 JSON 构建对象,避免手动拼凑属性。

5.6 图表库环境冲突导致演示崩溃

现象:为做可视化安装了多个第三方库,答辩现场打开项目运行时报 ImportError 或版本冲突。原因:开发过程中陆续装了不同版本的库,本机正常换台机器就挂。解决方法:在项目根目录生成requirements.txt,答辩前换台干净机器测试一遍运行。生成命令:python -m pip freeze > requirements.txt,新环境恢复时执行python -m pip install -r requirements.txt。这是工程化习惯,但它能直接避免答辩现场最大的尴尬。

6. 验收清单与演示技巧:让老师和面试官一眼看到工作量

期末项目交的不只是代码,还有演示过程和验收效果。一套好的演示逻辑,能把代码里那些隐藏的“工程感”暴露出来。建议按下述顺序演示:先运行程序,展示当前剩余车位;入场一辆车,展示车牌格式化处理和空位变化;查询停车场状态,展示字典遍历;出场结算,展示计费过程;关闭程序重新打开,展示数据恢复,证明持久化没白做。整个过程控制在 3 分钟内,每个步骤对应一个考核点,老师问什么你都能接得住。

演示步骤对应功能点演示时说的重点
入场 2 辆车容量管理、车牌格式化“入场时车牌自动转大写,剩余车位实时减少”
查询在场车辆字典遍历“用字典存储,查车时间复杂度 O(1)”
出场 1 辆车时长计算、计费规则“按小时向上取整,白天夜间不同单价”
重启程序持久化验证“每次操作实时写 JSON 文件,重启后数据还在”

老师大概率会问的三个问题:为什么用字典而不用列表存车辆?答案:通过车牌直接定位记录更高效。时间戳为什么用 float 而不是字符串?答案:时长计算直接做差,格式化输出时再转为可读时间。如果扩容到 1000 个车位,程序哪部分需要改?答案:容量字段的值,其余逻辑无需调整。前两个问题请从代码里找答案,第三个问题其实暗示了类的参数化设计是合理的。

你还可以给系统留一个扩展口子:在Car类里增加一个is_member字段,出场计费时会员打八折。这个功能的代码量不到十行,但能让程序看起来具备“业务成长性”。如果还有余力,可以顺带提一句“后续接入车牌摄像头识别后,只需要替换入场时的车牌输入方式”,但不要真去接 cv2 做图像识别,那属于另一个课题的体量。最后想说的是:期末项目最忌贪多,把出入场链路做扎实,数据落盘做干净,演示有逻辑,这已经足够拿一个体面的分数。整个流程里我最深的体会是:在动手前把“车辆状态怎么保存、什么时候同步到文件”想清楚,比后面补任何代码都省时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询