ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python实现智能停车管理系统:从对象建模到JSON持久化

Python实现智能停车管理系统:从对象建模到JSON持久化 简介面向Python初学者与K12学生的期末项目完整实现停车场模拟出入场管理。系统使用OpenCV完成车牌识别结合深度学习检测与OCR文字识别采用MySQL关系型数据库存储入场、出场和计费信息借助pygame构建可视化操作界面文档部分覆盖可行性分析、需求分析、概要设计、详细设计和软件测试全流程适合课程设计或自学练手参考。压缩包共12个文件以8个Python脚本为核心分别负责图形界面、车牌识别、计费算法、数据库读写等模块另附说明文档整体大小仅17KB结构轻量、便于阅读。业务上明确区分入场与出场流程入场识别车牌后写入数据库出场匹配最近一次入场记录并自动结算费用同时更新车位信息配套有E-R图与原型设计思路帮助理解业务逻辑与模块划分。学习热度方面已有760人学习下载。读者可直接运行或修改代码将其拓展至真实场景也可从中抽取车牌识别或数据库模块复用节省开发时间。1. 智能停车管理系统模拟出入场Python期末作业的经典考法很多同学的Python期末作业都会卡在同一种局面需求听起来太简单无非是“入场登记、出场缴费”可真要把这套智能停车管理系统跑起来又会发现要回答一连串问题——车位满了怎么办同一辆车重复入场怎么拦跨天停车费用怎么算程序一关记录还在吗这些问题的背面恰好就是python基础语法里的类、字典、时间处理、文件读写和流程控制。这套模拟系统不需要摄像头和道闸它把停车场抽象成两类状态哪些车位空闲、每个占用车位上停的是什么车。只要能把这套状态的数据流走通期末演示就能讲出完整的故事。下面我按自己常用的建摸和实现顺序把可运行的python代码和参数选择一起拆开。2. 智能停车管理系统的对象建模与出入场规则2.1 出入场模拟的本质是维护共享状态停车场的核心不是“车”而是“状态”。入场时要做三件事检查是否满位、分配一个车位号、写入车辆信息出场时反过来计算费用、删除车辆信息、归还车位号。换句话说所有逻辑都围绕一份共享状态展开。我一般不会只用列表来管理车位因为列表查找和删除都是线性扫描期末作业虽然数据量小但代码写出来不干净。更清晰的做法是组合使用set和dictfree_spots用集合存空闲车位occupied用字典存车位号到车辆对象的映射。入场时从free_spots弹出一个元素出场时再把它加回去。这样一台车占一个坑逻辑上不会出现同一个车位被分配给两台车的情况。class ParkingLot: def __init__(self, total_spots: int 10): self.total_spots total_spots self.free_spots set(range(1, total_spots 1)) self.occupied {} # key: 车位号, value: Car对象这段代码里total_spots是车位总数默认 10答辩时可以现场改成 3 来演示满位。range(1, total_spots 1)生成从 1 到 10 的车位号放入free_spots。occupied字典初值为空后续的park和leave方法都会改它。之所以用字典而不是列表是因为出场时要根据车牌查车位如果直接用列表存 Car 对象还得遍历才能找到车用字典可以做到“车位号 - 车”的反查。2.2 用类封装车辆信息和入场时间车辆对象至少要有两个字段车牌号和入场时间。入场时间必须用datetime对象而不是字符串因为出场计算费用时要对时间做减法。如果用字符串还得先解析成datetime属于给自己挖坑。from datetime import datetime class Car: def __init__(self, plate: str, enter_time: datetime None): self.plate plate self.enter_time enter_time or datetime.now()这里的默认参数enter_timeNone是刻意的正常运行时用datetime.now()自动取当前时间写测试用例时可以传入固定时间比如Car(京A12345, datetime(2025, 6, 1, 8, 0))这样就能验证第二天出场时的跨天费用不用真等上几个小时。这是一个很实用的期末答辩演示技巧提前把边界场景做成可控的。2.3 计费规则要放在独立函数里智能停车管理系统的计费规则常见做法是按“首小时 超时累加 日封顶”设计答辩时容易讲清楚也方便改单价。先定一个明确的规则首小时收费 5 元超过 1 小时、不足 8 小时的部分每小时加 2 元达到 8 小时但不足 24 小时按 20 元封顶跨天重新累计每日封顶。把这个规则提前写进文档比直接在leave方法里写一坨判断要专业得多。为了把这个规则落到代码里我单独写一个计费函数def calc_fee(enter_time: datetime, leave_time: datetime) - int: duration leave_time - enter_time total_seconds int(duration.total_seconds()) hours total_seconds // 3600 minutes (total_seconds % 3600) // 60 if minutes 0: hours 1 # 不满1小时按1小时计费 if hours 1: return 5 if hours 8: return 5 (hours - 1) * 2 return 20 # 1天内封顶20元这段代码的输入是入口时间和出口时间返回整数费用。total_seconds()把时间差转换成秒然后用整数除法算出完整小时数和剩余分钟数。注意最后若不整除分钟只要大于 0 就把小时数加一比如停了 1 小时 1 分钟按 2 小时计费。这种“向上取整”的算法很常见适合期末作业展示。参数enter_time和leave_time必须是datetime对象否则duration无法做减法。下面的表格总结了该规则在不同时长下的预期输出方便核对代码停车时长计算过程费用30 分钟不足1小时按1小时5元2 小时首小时5元 1小时×2元7元7 小时首小时5元 6小时×2元17元10 小时不满8小时是按封顶20元25 小时跨天第二天重新计算次日再计20元这里需要特别说明上面的函数只在单日逻辑内生效跨天时如果只传两个时间点计算出的总时长可能超过 24 小时费用就变成 20 元封顶这并不符合“每日封顶”的业务规则。因此我会在下一章把它拆成“按自然日分段计费”避免答辩时被老师一眼问穿。3. 用Python实现入场登记与出场结算核心流程3.1 入场登记分配空闲车位并写入字典入场动作要同时处理“满位检查”和“重复入场检查”。如果不检查同一车牌连续入场两次第二个车位也会被占用出场时只释放一个车位状态就乱了。所以我会在ParkingLot中增加park方法def park(self, car: Car) - int: if car.plate in [c.plate for c in self.occupied.values()]: raise ValueError(f车辆 {car.plate} 已在场内) if not self.free_spots: raise RuntimeError(车位已满) spot_id self.free_spots.pop() car.enter_time car.enter_time or datetime.now() self.occupied[spot_id] car return spot_idpark方法返回分配到的车位号调用方可以根据返回值打印“请将车辆停至3号车位”。第一行用列表推导式取当前场内所有车牌虽然数据量大时效率不高但期末作业演示足够直观。第二段用free_spots.pop()从集合中取出一个车位号set.pop()会移除并返回任意一个元素这里取到哪个车位无所谓。这样设计之后入场状态就只有一个入口了后续如果想增加“无牌车”或“月卡车辆”只需在Car上增加字段不需要改动主流程。3.2 出场结算计算时长、费用并释放车位出场方法的重点是“先结算后释放”。如果先释放车位再算费一旦计费函数抛出异常车辆就凭空消失了状态不一致。我习惯先取出Car对象再调用计费函数最后从字典中删除def leave(self, plate: str, leave_time: datetime None) - dict: leave_time leave_time or datetime.now() for spot_id, car in self.occupied.items(): if car.plate plate: fee calc_fee(car.enter_time, leave_time) del self.occupied[spot_id] self.free_spots.add(spot_id) return { plate: plate, enter_time: car.enter_time, leave_time: leave_time, spot_id: spot_id, fee: fee, } raise KeyError(f车辆 {plate} 未入场)这个方法用for循环遍历字典项因为occupied是“车位号 - Car”的映射而我们要按车牌查所以只能遍历。找到后立刻保存spot_id结算完成再del并add归还车位。返回的字典包含了完整的账单信息主程序可以直接格式化打印。注意leave_time参数同样允许外部传入这样测试跨天时就不用修改系统时钟了。3.3 一个最小可复现的模拟流程把上面几个类组合起来就能形成一次完整的入场出场模拟。下面这段代码可以直接复制到main.py里运行from datetime import datetime, timedelta if __name__ __main__: lot ParkingLot(total_spots3) car_a Car(京A12345, datetime(2025, 6, 1, 8, 0)) spot_a lot.park(car_a) print(f入场: {car_a.plate} - {spot_a}号车位, 入场时间 {car_a.enter_time}) car_b Car(沪B67890, datetime(2025, 6, 1, 9, 30)) spot_b lot.park(car_b) print(f入场: {car_b.plate} - {spot_b}号车位, 入场时间 {car_b.enter_time}) bill lot.leave(京A12345, datetime(2025, 6, 1, 10, 15)) print(f出场: {bill[plate]}, 费用 {bill[fee]}元)这段python代码演示了三步创建停车场对象、依次入场两辆车、让第一辆车出场。打印结果里京A12345在 8:00 入场、10:15 出场时长 2 小时 15 分按向上取整规则应算 3 小时费用是 5 2×2 9 元。你可以在终端执行python main.py验证输出是否符合预期。若想进一步看到其余车位的状态可以在lot.park后打印lot.free_spots和lot.occupied确认状态变化的正确性。4. 数据持久化与命令行交互4.1 用JSON文件保存车辆状态避免“断电清零”期末演示时如果程序一关闭停车记录就全部消失老师很可能会追问“真实系统是怎么存储的”。因此我会在这个阶段加入 JSON 持久化。JSON 是 python 标准库不需要安装第三方依赖且结构清晰很适合期末作业。需要解决的一个小问题是datetime对象不能直接写入 JSON需要先转换成字符串读取时再恢复成datetime。import json from pathlib import Path def save_data(lot: ParkingLot, path: str parking_data.json) - None: payload { free_spots: list(lot.free_spots), occupied: { spot_id: { plate: car.plate, enter_time: car.enter_time.isoformat() } for spot_id, car in lot.occupied.items() } } Path(path).write_text(json.dumps(payload, ensure_asciiFalse, indent2), encodingutf-8)代码中的isoformat()把datetime转成2025-06-01T08:00:00这样的字符串。json.dumps的ensure_asciiFalse可以让车牌中的中文正常存储indent2让文件可读。free_spots是集合转成列表才能被 JSON 序列化。加载时再转回集合def load_data(path: str parking_data.json) - ParkingLot: if not Path(path).exists(): return ParkingLot() raw json.loads(Path(path).read_text(encodingutf-8)) lot ParkingLot() lot.free_spots set(raw[free_spots]) for spot_id, car_meta in raw[occupied].items(): car Car(car_meta[plate], datetime.fromisoformat(car_meta[enter_time])) lot.occupied[int(spot_id)] car return lot一个关键细节是从 JSON 读取得到的occupied字典键是字符串因为 JSON 的 key 只能是字符串所以外面用int(spot_id)把它转回整数否则后续park和leave方法中的比较会因类型不一致而出错。load_data在启动时调用save_data在每次变动后调用就能实现最简单的持久化。4.2 命令行菜单让答辩演示不再手忙脚乱纯函数调用让代码正确而命令行菜单让程序看起来“像个系统”。我会做一个 while 循环用input()接收指令支持entry、exit、status、save、quit五个命令。下面的代码省略了异常处理只展示核心分支def main(): lot load_data() print(欢迎使用智能停车管理系统可用命令: entry / exit / status / save / quit) while True: cmd input( ).strip().lower() if cmd quit: save_data(lot) break elif cmd status: print(f剩余车位: {len(lot.free_spots)}/{lot.total_spots}) for spot_id, car in lot.occupied.items(): print(f车位{spot_id}: {car.plate} 入场 {car.enter_time}) elif cmd entry: plate input(请输入车牌号: ).strip() try: spot lot.park(Car(plate)) print(f停车成功请前往{spot}号车位) save_data(lot) except (ValueError, RuntimeError) as e: print(e) elif cmd exit: plate input(请输入车牌号: ).strip() try: bill lot.leave(plate) print(f车辆{bill[plate]}在{bill[spot_id]}号车位停车费{bill[fee]}元) save_data(lot) except KeyError as e: print(e) else: print(未知命令)这段代码把第 2、3 章的逻辑串起来了。注意每一条成功的entry或exit之后都立即save_data这样即使程序在保存后崩溃数据也不会丢太多。cmd status分支会列出场内所有车辆答辩时可以先执行entry停两辆车再执行status展示状态变化最后exit展示结算账单演示流程非常顺畅。4.3 本地运行时的python环境配置细节如果你刚做python入门可能会卡在运行阶段。我的做法是先用 vscode 打开项目文件夹确认右下角解释器选的是当前项目的 python 环境。装 python 之后不需要额外安装任何库因为这里只用了json、pathlib、datetime三个标准模块。如果运行时提示找不到python命令多半是环境变量没配好可以先在终端执行python --version检查。对于期末作业把main.py和parking_data.json放在同一目录下就足够了不要用虚拟环境尽量让老师克隆下来就能直接跑。5. 期末答辩加分技巧时间边界、费用精度与演示数据5.1 跨天时间计费把函数拆成按日分段我在 2.3 留了一个坑calc_fee对超过 24 小时的停车直接返回 20 元封顶这在业务上不对。真实停车场是每日封顶因此需要在计费函数里按天拆分。常见做法是循环切出每个自然日的时间段逐日调用单日费用函数最后累加。下面的代码能正确处理连续停多天的情况def calc_fee_multi_day(enter_time: datetime, leave_time: datetime) - int: if leave_time.date() enter_time.date(): return calc_fee(enter_time, leave_time) total_fee 0 current_day_start enter_time while current_day_start.date() leave_time.date(): day_end datetime(current_day_start.year, current_day_start.month, current_day_start.day, 23, 59, 59) total_fee calc_fee(current_day_start, day_end) current_day_start day_end timedelta(seconds1) total_fee calc_fee(current_day_start, leave_time) return total_fee这段python代码用一个while循环把时间切成若干段第一天从入场时间到当日 23:59:59后续每天从 00:00:00 到当日 23:59:59最后一天从当天零点到出场时间。每段调用原来的calc_fee单日计费。答辩时重点讲这个递归拆分的思路比直接背代码更能体现你对边界条件的理解。5.2 费用结算避免浮点数陷阱计费金额不要用 float。停车费率通常是整数用int计算结果就是整数不会出现0.1 0.2这种经典精度问题。如果以后要支持打折比如月卡 8 折建议用Decimal而不是float。我在calc_fee中返回的是int这样在打印时直接用f{bill[fee]}元不会出现 7.999999 这样的尴尬结果。如果你非要支持小数金额在转换成str前用round(value, 2)兜底。5.3 准备一组可复现的演示数据期末演示最怕临场输入车牌号时手抖。我会在load_data后面追加一个调试开关当parking_data.json不存在且环境变量DEMO_MODE1时自动创建三辆车的模拟数据。这样每次演示都能从同一个状态开始老师看到的数据是稳定的。一个更简单的方式是专门写一个seed_demo.py它先创建ParkingLot手动加入几辆入场时间不同的车再调用save_data。演示前先运行python seed_demo.py再启动main.py查看就能快速展示“按时间长短收费不同”的场景。5.4 答辩时怎么展示这段代码我建议按三步走先运行status让老师看到当前状态再执行entry展示入场分配车位最后执行exit展示出场计费。每一步都顺手打开parking_data.json指出文件里哪些字段对应屏幕上的输出。这套连贯动作能同时证明你会写类、会处理时间、会操作文件——正好是对 python 基础语法里的核心点做了一次完整展示。所有代码保持在一屏能看完的粒度函数不超过 20 行这样现场讲解时不需要来回滚动翻页。本文还有配套的精品资源点击获取
返回列表