简介:这份Python期末作业资源实现了一套智能停车管理系统,模拟车辆出入场全流程,面向高校计算机相关专业学生、Python初学者及需要课程设计参考的开发者。系统以OpenCV车牌识别为核心,结合YOLO+OCR深度学习方案,通过MySQL完成入场信息、出场计费与车位状态的存储查询,并用pygame搭建可视化窗体,完整覆盖可行性分析、需求分析、概要设计、详细设计与软件测试等环节。压缩包共12个文件,包含8个py源码文件、3个keep占位文件及1个说明txt,整体约17KB,源码按gui、database、license_plate、billing等模块拆分,结构清晰便于阅读与二次修改。目前已有762人学习下载,适合作为期末大作业或课程设计模板,帮助读者快速理解车牌识别、数据库设计与计费算法之间的协作方式,并在此基础上完成功能扩展与调试。
1. 从一份期末作业说起:智能停车管理系统到底在模拟什么
很多人第一次看到「Python期末作业-智能停车管理系统(模拟出入场)」这个题目,第一反应是「不就是个增删改查吗」。真动手写才发现,难点根本不在增删改查,而在于状态机:一辆车从进场到出场,中间经历了车位分配、计时、计费、状态流转,任何一个环节没设计好,最后对账就是一笔糊涂账。这个标题要解决的核心问题,是用 Python 模拟一个停车场从车辆入场、占用车位、计时到出场结算的完整闭环,适合正在做课程设计的学生,也适合想练手面向对象和数据结构的新手。它不需要真实硬件,不需要摄像头,纯逻辑模拟,但麻雀虽小,五脏俱全。下面我按自己带学生做这类作业的经验,把选型、实现、参数和坑一次讲透。
2. 先定数据结构再写代码:车位、车辆、订单三张表怎么设计
2.1 为什么用类而不是字典堆
新手最容易犯的错,是拿一个大字典存所有状态,比如parking = {"A01": "京A12345", ...}。这样写前二十分钟很爽,等到要算停车费、要查历史记录、要区分临时车和月租车,字典就变成了黑匣子,改一处崩三处。常见做法是用三个类把职责切开:ParkingSpot管车位状态,Vehicle管车辆信息,ParkingLot管调度和计费。这样每个类的字段含义清晰,调试时打印一个对象就能看到全部属性,不用在一堆嵌套字典里翻。
选型理由很直接:期末作业的评分点通常包括「代码结构清晰」「功能完整」「有异常处理」。用类天然满足第一条,而且后面加功能(比如预约车位、会员折扣)只需要扩展类,不用重写主循环。我一般会建议学生先把三个类的字段列在纸上,确认没有遗漏再敲代码,比边写边改省一半时间。
2.2 三个核心类的字段与职责
先看车位类。车位需要知道自己的编号、类型(小型/大型)、当前是否被占用、停的是哪辆车。车辆类需要车牌号、进场时间、车辆类型。停车场类持有所有车位和一个「车牌到车位」的映射,方便出场时快速定位。
from datetime import datetime class ParkingSpot: def __init__(self, spot_id, spot_type="small"): self.spot_id = spot_id # 车位编号,如 "A01" self.spot_type = spot_type # small / large self.is_occupied = False # 占用状态 self.vehicle_plate = None # 当前停放车辆车牌 def assign(self, plate): # 占用车位,记录车牌 self.is_occupied = True self.vehicle_plate = plate def release(self): # 释放车位,清空车牌 self.is_occupied = False self.vehicle_plate = None class Vehicle: def __init__(self, plate, v_type="small"): self.plate = plate # 车牌号 self.v_type = v_type # 车辆类型 self.entry_time = None # 进场时间 self.exit_time = None # 出场时间 def check_in(self): self.entry_time = datetime.now() def check_out(self): self.exit_time = datetime.now()逻辑说明:ParkingSpot的assign和release成对出现,保证状态不会残留。Vehicle的进出场时间用datetime.now()记录,精度到秒,足够模拟用。参数上,spot_type和v_type默认都是small,如果要做大小车分流,初始化停车场时传入不同列表即可。注意entry_time初始为None,出场计费前必须判空,否则会报TypeError,这是血泪经验。
2.3 停车场主类的调度逻辑
停车场类负责把车位和车辆串起来。核心方法有三个:park入场、leave出场、find_spot找空位。找空位时按编号顺序遍历,先到先得,符合真实停车场习惯。
class ParkingLot: def __init__(self, small_count=10, large_count=3): self.spots = [] # 生成小型车位 A01-A10 for i in range(1, small_count + 1): self.spots.append(ParkingSpot(f"A{i:02d}", "small")) # 生成大型车位 B01-B03 for i in range(1, large_count + 1): self.spots.append(ParkingSpot(f"B{i:02d}", "large")) self.plate_to_spot = {} # 车牌 -> 车位 映射 self.history = [] # 历史订单 def find_spot(self, v_type): # 按类型找第一个空位 for spot in self.spots: if not spot.is_occupied and spot.spot_type == v_type: return spot return None def park(self, plate, v_type="small"): if plate in self.plate_to_spot: return f"车牌 {plate} 已在场内" spot = self.find_spot(v_type) if spot is None: return f"没有空闲的{v_type}车位" vehicle = Vehicle(plate, v_type) vehicle.check_in() spot.assign(plate) self.plate_to_spot[plate] = spot return f"{plate} 已停入 {spot.spot_id},时间 {vehicle.entry_time:%H:%M:%S}"逻辑说明:find_spot用线性遍历,车位数量在几十个以内性能无压力。park先查重再找位,避免同一车牌重复入场。参数small_count和large_count控制车位规模,改这两个数就能模拟不同大小的停车场。返回字符串而不是布尔值,是为了在主循环里直接打印,省去额外判断。
3. 模拟出入场:计时、计费与状态流转怎么落地
3.1 出场结算的完整流程
出场比入场复杂,因为要算钱。流程是:根据车牌找到车位,记录出场时间,计算停车时长,按时长和费率算费,释放车位,写入历史。这里有个容易翻车的点:时长计算要用total_seconds()而不是直接减seconds,因为timedelta.seconds只返回不足一天的部分,超过 24 小时会算错。
import math class ParkingLot: # 接上面的类,补充出场方法 def leave(self, plate, rate_per_hour=5.0): if plate not in self.plate_to_spot: return f"车牌 {plate} 不在场内" spot = self.plate_to_spot[plate] vehicle = Vehicle(plate, spot.spot_type) # 从车位反查进场时间需要额外存储,这里用简化方式 # 实际应维护 plate -> Vehicle 映射 vehicle.check_out() duration = (vehicle.exit_time - vehicle.entry_time).total_seconds() hours = math.ceil(duration / 3600) # 不足一小时按一小时 fee = hours * rate_per_hour spot.release() del self.plate_to_spot[plate] self.history.append({ "plate": plate, "spot": spot.spot_id, "hours": hours, "fee": fee }) return f"{plate} 出场,停车 {hours} 小时,费用 {fee:.2f} 元"逻辑说明:math.ceil实现「不足一小时按一小时」,这是大多数停车场的规则。参数rate_per_hour默认 5 元,改这个数就能模拟不同城市费率。注意上面代码里vehicle.entry_time是新建对象后为None,真实实现必须维护plate -> Vehicle的映射,把入场时的车辆对象存下来,否则出场时拿不到进场时间。这个坑我在学生作业里见过太多次,现象是TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'NoneType',原因就是没存车辆对象,解决方法是加一个self.vehicles = {}字典。
3.2 用命令行主循环串起所有操作
模拟出入场最直观的方式是命令行交互。用input读指令,支持in、out、status、quit四个命令。主循环要处理非法输入,不能一输错就崩。
def main(): lot = ParkingLot(small_count=5, large_count=2) print("智能停车管理系统(模拟)") print("命令:in 车牌 / out 车牌 / status / quit") while True: cmd = input("> ").strip().split() if not cmd: continue action = cmd[0].lower() if action == "quit": print("系统关闭") break elif action == "in" and len(cmd) == 2: print(lot.park(cmd[1])) elif action == "out" and len(cmd) == 2: print(lot.leave(cmd[1])) elif action == "status": free = sum(1 for s in lot.spots if not s.is_occupied) print(f"空闲车位:{free}/{len(lot.spots)}") else: print("无效命令,请重新输入") if __name__ == "__main__": main()逻辑说明:split()把输入按空格切开,action取第一个词。len(cmd) == 2保证车牌参数存在,避免索引越界。status用生成器表达式统计空闲车位,一行搞定。参数上,small_count=5方便测试,真实场景可以调大。这个主循环结构清晰,加新命令只需加一个elif分支,扩展性好。
3.3 数据持久化:把订单写进文件
期末作业通常要求数据能保存,下次运行还能看到历史。最简单的方式是写 JSON 文件。用json.dump把history列表存下来,启动时读回。
import json import os def save_history(lot, filename="history.json"): # 把历史订单写入 JSON 文件 with open(filename, "w", encoding="utf-8") as f: json.dump(lot.history, f, ensure_ascii=False, indent=2) def load_history(filename="history.json"): # 读取历史订单,文件不存在返回空列表 if not os.path.exists(filename): return [] with open(filename, "r", encoding="utf-8") as f: return json.load(f)逻辑说明:ensure_ascii=False保证中文车牌正常显示,不加这个参数会变成\uXXXX转义。indent=2让文件可读。参数filename可改,方便多停车场分开存。注意load_history要先判断文件是否存在,否则首次运行会报FileNotFoundError,这是新手常见翻车点。
4. 避坑与排查:模拟出入场最容易翻车的 5 个地方
4.1 现象:出场时报 NoneType 错误
原因:出场时新建了Vehicle对象,但没把入场时的对象存下来,entry_time是None,相减就崩。解决:在ParkingLot里加self.vehicles = {},park时存self.vehicles[plate] = vehicle,leave时取出来用,不要重新 new。
4.2 现象:停车超过 24 小时费用算少
原因:用了timedelta.seconds而不是total_seconds()。seconds只返回不足一天的部分,25 小时会变成 1 小时。解决:统一用total_seconds(),再除以 3600。
4.3 现象:同一车牌能重复入场
原因:park方法没查重,直接找空位。解决:入场前先判断plate in self.plate_to_spot,在場内就拒绝。这个逻辑一行代码,但不写就是 bug。
4.4 现象:中文车牌存文件后变乱码
原因:json.dump默认ensure_ascii=True,中文被转义。解决:加ensure_ascii=False,同时open时指定encoding="utf-8"。两个地方都要改,只改一个还是乱。
4.5 现象:车位释放后状态没清干净
原因:release只改了is_occupied,忘了清vehicle_plate。解决:release里两行都写,或者直接重新初始化车位对象。建议写单元测试,释放后断言vehicle_plate is None。
5. 进阶技巧:用状态机重构让代码能扛住需求变更
写到这儿,基本功能已经能跑。但如果老师加需求——比如「月租车免费」「预约车位保留 15 分钟」「出场前先缴费」——你会发现原来的if-else越堆越乱。我一般会在这个阶段引入一个轻量状态机,把车辆状态显式化:WAITING(在场外)、PARKED(已停入)、LEAVING(出场中)、DONE(已离场)。每次操作只允许特定状态迁移,非法迁移直接拒绝。
from enum import Enum class CarState(Enum): WAITING = "waiting" PARKED = "parked" LEAVING = "leaving" DONE = "done" # 合法迁移表:当前状态 -> 允许的下一状态 TRANSITIONS = { CarState.WAITING: [CarState.PARKED], CarState.PARKED: [CarState.LEAVING], CarState.LEAVING: [CarState.DONE], CarState.DONE: [] } def can_transition(cur, nxt): return nxt in TRANSITIONS.get(cur, [])逻辑说明:Enum定义状态,TRANSITIONS字典描述合法迁移。can_transition做校验,返回布尔值。这样加新状态只需改字典,不用动业务代码。参数上,状态名用英文常量,避免拼写错误。这个模式在真实停车系统里很常见,期末作业用上能明显加分。
验证方法也简单:写一个测试脚本,模拟「入场→出场→再入场」的完整流程,断言每一步状态正确。如果状态机写对了,重复入场会被WAITING -> PARKED之外的迁移拦住。我自己的习惯是每加一个功能就先写三行断言,跑通了再往下写,比事后调试省心得多。这个方案值不值得做?如果你只是交作业,基础版足够;如果你想拿它当简历项目,状态机加持久化加单元测试,能撑起一个像样的后端雏形。希望帮到你。
本文还有配套的精品资源,点击获取