Python技术迷

用Python实现一个电影订票系统!

用 Python 写个电影订票系统,说难也不难,说简单也有一堆小细节,尤其是选座那块,稍不注意就乱了套。下面就按“一个脚本就能跑起来”的思路,把核心流程撸一遍,中间穿插代码,你照着改一改就能成自己用的小工具。

电影订票最基本就那么几步:

  • 看有哪些电影、哪些场次
  • 选一个场次
  • 打开座位图,挑座位
  • 下单,占住座位

我们先不搞复杂的用户登录、支付、退票之类,把“选座 + 占座”打通就够了,后面你要扩展成 Web、接数据库啥的,再往上堆就行。

所以可以抽象出几个核心概念:

  • 电影场次:放什么片、几点、在哪个影厅
  • 座位:在第几排第几列,是否已经被占
  • 订票逻辑:给定场次 + 座位,把它标记为已售出

用 Python 最自然的方式就是给“场次”做一个类,把座位信息也塞进去。

来一个比较轻的类,别搞太复杂,命令行玩玩够用了:

from typing import List

classScreening:
def__init__(self, sid: int, movie_title: str, time: str, hall: str,
                 rows: int = 5, cols: int = 8)
:

        self.sid = sid
        self.movie_title = movie_title
        self.time = time
        self.hall = hall
        self.rows = rows
        self.cols = cols
# 0 表示空座,1 表示已售
        self.seats: List[List[int]] = [
            [0for _ in range(cols)] for _ in range(rows)
        ]

defshow_seats(self):
        print(f"\n场次 {self.sid} - 《{self.movie_title}》 {self.time}{self.hall}")
        print("座位图(O=空位,X=已售):")
# 打印列号
        print("    ", end="")
for c in range(1, self.cols + 1):
            print(f"{c:2d}", end=" ")
        print()
# 打印每一排
for r in range(self.rows):
            print(f"排{r + 1:2d} ", end="")
for c in range(self.cols):
                symbol = "O"if self.seats[r][c] == 0else"X"
                print(f" {symbol} ", end="")
            print()

defbook_seat(self, row: int, col: int) -> bool:
"""row、col 传进来用 1 开始的人类习惯,内部转成 0 开始"""
# 越界校验
ifnot (1 <= row <= self.rows and1 <= col <= self.cols):
            print("座标不对,超出座位范围了")
returnFalse

        r_idx = row - 1
        c_idx = col - 1

if self.seats[r_idx][c_idx] == 1:
            print("这个位置已经被订走了,换一个吧")
returnFalse

        self.seats[r_idx][c_idx] = 1
        print(f"订座成功:{row} 排 {col} 座")
returnTrue

这个类解决了三件事:

  1. 把一场电影的基础信息存起来
  2. 在内存里维护一张二维座位表
  3. 提供两个操作:展示座位、订座位

只要这个东西好用,外面的系统就是一层壳。

单个场次搞定了,还得有个“系统入口”,把多个场次挂在一起,再给用户提供命令行菜单。

比如来个 TicketSystem 类:

classTicketSystem:
def__init__(self):
        self.screenings = {}
        self._init_demo_data()

def_init_demo_data(self):
# 这里先写死几场,实际可以从数据库、接口里面加载
        s1 = Screening(1, "星际穿越", "2024-06-01 19:00", "1号厅", rows=6, cols=8)
        s2 = Screening(2, "盗梦空间", "2024-06-01 20:30", "2号厅", rows=5, cols=6)
        s3 = Screening(3, "流浪地球 2", "2024-06-01 21:00", "3号厅", rows=7, cols=10)
        self.screenings[s1.sid] = s1
        self.screenings[s2.sid] = s2
        self.screenings[s3.sid] = s3

deflist_screenings(self):
        print("\n当前可选场次:")
for sid, s in self.screenings.items():
            print(f"{sid}. 《{s.movie_title}》 {s.time}{s.hall} "
f"({s.rows}排 x {s.cols}列)")

defchoose_screening(self) -> Screening | None:
        self.list_screenings()
        raw = input("请输入要查看的场次编号(q 返回):").strip()
if raw.lower() == "q":
returnNone
ifnot raw.isdigit():
            print("输入的不是数字")
returnNone
        sid = int(raw)
        screening = self.screenings.get(sid)
ifnot screening:
            print("没有这个场次编号")
returnNone
return screening

defrun(self):
whileTrue:
            print("\n==== 简易电影订票系统 ====")
            print("1. 查看场次列表")
            print("2. 查看场次座位并订票")
            print("3. 退出系统")
            choice = input("请选择功能:").strip()

if choice == "1":
                self.list_screenings()
elif choice == "2":
                self.handle_booking()
elif choice == "3":
                print("拜拜~")
break
else:
                print("没这个选项,再来一次")

defhandle_booking(self):
        screening = self.choose_screening()
ifnot screening:
return
# 展示座位
        screening.show_seats()
# 选座
        row_raw = input("请输入要订的排号(数字,q 取消):").strip()
if row_raw.lower() == "q":
return
        col_raw = input("请输入要订的列号(数字,q 取消):").strip()
if col_raw.lower() == "q":
return
ifnot (row_raw.isdigit() and col_raw.isdigit()):
            print("排号/列号必须是数字")
return
        row = int(row_raw)
        col = int(col_raw)
        screening.book_seat(row, col)
# 订完再显示一次座位图
        screening.show_seats()

最后别忘了加个脚本入口:

if __name__ == "__main__":
    system = TicketSystem()
    system.run()

丢到一个 ticket_system.py 里,在终端里:python ticket_system.py,就能跑起来了。

上面那个版本已经能用,不过选座体验还比较原始,比如:

  • 只能一次选一个座位
  • 没有“连坐”检测
  • 没有限制一场订太多

这些都可以在 Screening 里继续扩展,比如一次支持订多个座位,可以用一个小函数解析类似 “3-5,3-6,4-7” 这种输入。

举个多选座的简单版本:

defparse_seat_list(seat_str: str):
"""
    seat_str 形如:'3-5,3-6,4-7'
    返回列表 [(3,5), (3,6), (4,7)]
    """

    result = []
    parts = seat_str.split(",")
for p in parts:
        p = p.strip()
ifnot p:
continue
if"-"notin p:
            print(f"格式不对:{p},应该是 '排-列'")
returnNone
        r_str, c_str = p.split("-", 1)
ifnot (r_str.isdigit() and c_str.isdigit()):
            print(f"格式不对:{p},排和列必须是数字")
returnNone
        result.append((int(r_str), int(c_str)))
return result

然后在 handle_booking 里面改一下逻辑:

defhandle_booking(self):
    screening = self.choose_screening()
ifnot screening:
return
    screening.show_seats()
    seat_str = input("请输入要订的座位,例如 3-5,3-6 (q 取消):").strip()
if seat_str.lower() == "q":
return
    seats = parse_seat_list(seat_str)
if seats isNone:
return

# 先整体校验一遍,避免一半成功一半失败
for row, col in seats:
ifnot (1 <= row <= screening.rows and1 <= col <= screening.cols):
            print(f"{row}-{col} 超出范围,整单取消")
return
if screening.seats[row - 1][col - 1] == 1:
            print(f"{row}-{col} 已被占用,整单取消")
return

# 校验通过再正式落位
for row, col in seats:
        screening.book_seat(row, col)

    screening.show_seats()

这样起码能做到“要么全部成功,要么全不订”,避免出现用户说“我刚刚选的那几张票怎么只扣了一部分”。

当前这套是纯内存版,你一退出程序,所有订票记录就没了。如果只是为了写作业、练手,问题不大。如果想稍微认真一点,可以弄个最简单的落地方式,比如:

  • 用 JSON 文件存每个场次的座位矩阵
  • 程序启动时从 JSON 读进来,退出时再写回去

给一个非常简化的示例,大概这样:

import json
from pathlib import Path

classTicketSystem:
def__init__(self, data_file: str = "data.json"):
        self.data_file = Path(data_file)
        self.screenings = {}
if self.data_file.exists():
            self._load_from_file()
else:
            self._init_demo_data()
            self._save_to_file()

def_save_to_file(self):
        data = {}
for sid, s in self.screenings.items():
            data[sid] = {
"movie_title": s.movie_title,
"time": s.time,
"hall": s.hall,
"rows": s.rows,
"cols": s.cols,
"seats": s.seats,
            }
        self.data_file.write_text(json.dumps(data, ensure_ascii=False, indent=2))

def_load_from_file(self):
        raw = self.data_file.read_text(encoding="utf-8")
        data = json.loads(raw)
for sid_str, info in data.items():
            sid = int(sid_str)
            s = Screening(
                sid,
                info["movie_title"],
                info["time"],
                info["hall"],
                info["rows"],
                info["cols"],
            )
            s.seats = info["seats"]
            self.screenings[sid] = s

# 记得在订票成功后调用 _save_to_file

然后在 book_seat 或者 handle_booking 那里,只要订票成功就调用一次 _save_to_file(),就能把新状态写回磁盘。虽然很粗糙,但足够支撑“下次打开还能看到之前的座位情况”。

等这个命令行版本跑顺了,再往上加花活就容易了,比如:

  • 用 Flask / FastAPI 做成 Web 接口,前端页面用 Ajax 拿座位图
  • 引入数据库,把场次和订单分开建表
  • 加上用户系统,谁订的票、一个账号限购几张
  • 加一个“锁座超时自动释放”的小任务

这些本质上都是在现在这套“核心模型”的基础上往外扩,真正要保证不乱的,还是 Screening 里那张二维座位表和几条订票规则。

你可以先把上面的代码扔进一个文件里跑一跑,习惯一下这个数据流,后面要接 Web、接数据库的时候,就不会觉得逻辑特别散。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB