parent
2ad1efbadf
commit
0507891046
@ -1,259 +0,0 @@
|
||||
import csv
|
||||
import json
|
||||
import sqlite3
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import date
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
|
||||
|
||||
class Student:
|
||||
def __init__(self, name: str, id_card: str, stu_id: str, gender: Optional[bool] = None,
|
||||
height: Optional[int] = None, weight: Optional[float] = None,
|
||||
enrollment_date: Optional[Union[date, str]] = None,
|
||||
class_name: Optional[str] = None, major: Optional[str] = None):
|
||||
self.name = name
|
||||
self.id_card = id_card
|
||||
self.stu_id = stu_id
|
||||
self.gender = gender
|
||||
self.height = height
|
||||
self.weight = weight
|
||||
self.enrollment_date = date.fromisoformat(enrollment_date) if isinstance(enrollment_date,
|
||||
str) else enrollment_date
|
||||
self.class_name = class_name
|
||||
self.major = major
|
||||
self.age, self.birthday = self._calculate_age_and_birthday()
|
||||
self._validation_errors = self._validate_all()
|
||||
|
||||
def _calculate_age_and_birthday(self) -> tuple[int, date]:
|
||||
birth_year, birth_month, birth_day = map(int, (self.id_card[6:10], self.id_card[10:12], self.id_card[12:14]))
|
||||
birthday = date(birth_year, birth_month, birth_day)
|
||||
today = date.today()
|
||||
age = today.year - birth_year - ((today.month, today.day) < (birth_month, birth_day))
|
||||
return age, birthday
|
||||
|
||||
def _validate_all(self) -> List[str]:
|
||||
errors = []
|
||||
if len(self.id_card) != 18 or not self.id_card[:17].isdigit():
|
||||
errors.append("身份证号必须为18位数字")
|
||||
if not self.stu_id:
|
||||
errors.append("学号不能为空")
|
||||
if not (2 <= len(self.name) <= 20) or not self.name.isalpha():
|
||||
errors.append("姓名需为2-20个字母")
|
||||
if self.enrollment_date and self.enrollment_date < self.birthday:
|
||||
errors.append("入学日期不能早于出生日期")
|
||||
if self.height and not 50 <= self.height <= 250:
|
||||
errors.append("身高需在50-250cm之间")
|
||||
if self.weight and not 5 <= self.weight <= 300:
|
||||
errors.append("体重需在5-300kg之间")
|
||||
return errors
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return not self._validation_errors
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'name': self.name, 'id_card': self.id_card, 'stu_id': self.stu_id,
|
||||
'gender': self.gender, 'height': self.height, 'weight': self.weight,
|
||||
'enrollment_date': self.enrollment_date.isoformat() if self.enrollment_date else None,
|
||||
'class_name': self.class_name, 'major': self.major,
|
||||
'age': self.age, 'birthday': self.birthday.isoformat()
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'Student':
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class IStudentDAL(ABC):
|
||||
@abstractmethod
|
||||
def get_by_id(self, id_card: str) -> Optional[Student]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Student]: ...
|
||||
|
||||
@abstractmethod
|
||||
def get_all(self) -> List[Student]: ...
|
||||
|
||||
@abstractmethod
|
||||
def add(self, student: Student) -> bool: ...
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, id_card: str = None, stu_id: str = None) -> bool: ...
|
||||
|
||||
@abstractmethod
|
||||
def update(self, student: Student) -> bool: ...
|
||||
|
||||
|
||||
class BaseDAL(IStudentDAL):
|
||||
def __init__(self, file_path: str):
|
||||
self.file_path = file_path
|
||||
self._init_storage()
|
||||
|
||||
def _init_storage(self): pass
|
||||
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
||||
return next((s for s in self.get_all() if s.stu_id == stu_id), None)
|
||||
|
||||
|
||||
class CSVStudentDAL(BaseDAL):
|
||||
def _init_storage(self):
|
||||
with open(self.file_path, 'a') as f: pass # Ensure file exists
|
||||
|
||||
def get_all(self) -> List[Student]:
|
||||
with open(self.file_path, 'r') as f:
|
||||
return [Student.from_dict(row) for row in csv.DictReader(f)]
|
||||
|
||||
def add(self, student: Student) -> bool:
|
||||
if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id):
|
||||
return False
|
||||
with open(self.file_path, 'a', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=student.to_dict().keys())
|
||||
if f.tell() == 0: writer.writeheader()
|
||||
writer.writerow(student.to_dict())
|
||||
return True
|
||||
|
||||
def delete(self, id_card: str = None, stu_id: str = None) -> bool:
|
||||
students = [s for s in self.get_all()
|
||||
if not ((id_card and s.id_card == id_card) or (stu_id and s.stu_id == stu_id))]
|
||||
with open(self.file_path, 'w', newline='') as f:
|
||||
if students:
|
||||
writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(s.to_dict() for s in students)
|
||||
return len(students) < len(self.get_all())
|
||||
|
||||
|
||||
class JSONStudentDAL(BaseDAL):
|
||||
def _init_storage(self):
|
||||
with open(self.file_path, 'a') as f: pass
|
||||
|
||||
def get_all(self) -> List[Student]:
|
||||
with open(self.file_path, 'r') as f:
|
||||
return [Student.from_dict(row) for row in json.load(f)]
|
||||
|
||||
def add(self, student: Student) -> bool:
|
||||
data = self.get_all()
|
||||
if any(s.id_card == student.id_card or s.stu_id == student.stu_id for s in data):
|
||||
return False
|
||||
data.append(student)
|
||||
self._save_all(data)
|
||||
return True
|
||||
|
||||
def _save_all(self, data: List[Student]):
|
||||
with open(self.file_path, 'w') as f:
|
||||
json.dump([s.to_dict() for s in data], f, indent=2)
|
||||
|
||||
|
||||
class SQLiteStudentDAL(BaseDAL):
|
||||
def _init_storage(self):
|
||||
with sqlite3.connect(self.file_path) as conn:
|
||||
conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS students (
|
||||
id_card TEXT PRIMARY KEY, stu_id TEXT UNIQUE, name TEXT,
|
||||
gender BOOLEAN, height INTEGER, weight REAL,
|
||||
enrollment_date TEXT, class_name TEXT, major TEXT,
|
||||
age INTEGER, birthday TEXT)''')
|
||||
|
||||
def get_all(self) -> List[Student]:
|
||||
with sqlite3.connect(self.file_path) as conn:
|
||||
rows = conn.execute("SELECT * FROM students").fetchall()
|
||||
return [Student.from_dict(dict(zip(('id_card', 'stu_id', 'name', 'gender', 'height',
|
||||
'weight', 'enrollment_date', 'class_name', 'major',
|
||||
'age', 'birthday'), row))) for row in rows]
|
||||
|
||||
def add(self, student: Student) -> bool:
|
||||
try:
|
||||
with sqlite3.connect(self.file_path) as conn:
|
||||
conn.execute('''
|
||||
INSERT INTO students VALUES (?,?,?,?,?,?,?,?,?,?,?)''',
|
||||
(student.id_card, student.stu_id, student.name, student.gender,
|
||||
student.height, student.weight,
|
||||
student.enrollment_date.isoformat() if student.enrollment_date else None,
|
||||
student.class_name, student.major, student.age, student.birthday.isoformat()))
|
||||
return True
|
||||
except sqlite3.IntegrityError:
|
||||
return False
|
||||
|
||||
|
||||
class StudentService:
|
||||
def __init__(self, dal: IStudentDAL):
|
||||
self.dal = dal
|
||||
|
||||
def add_student(self, student: Student) -> bool:
|
||||
if not student.is_valid:
|
||||
raise ValueError(f"Invalid student: {', '.join(student._validation_errors)}")
|
||||
return self.dal.add(student)
|
||||
|
||||
def get_all(self) -> List[Student]:
|
||||
return self.dal.get_all()
|
||||
|
||||
def query(self, **kwargs) -> List[Student]:
|
||||
return [s for s in self.get_all()
|
||||
if all(getattr(s, k, None) == v for k, v in kwargs.items() if v)]
|
||||
|
||||
def get_stats(self, field: str) -> Dict[str, int]:
|
||||
return {v: sum(1 for s in self.get_all() if getattr(s, field) == v)
|
||||
for v in set(getattr(s, field) for s in self.get_all())}
|
||||
|
||||
|
||||
class StudentCLI:
|
||||
def __init__(self, service: StudentService):
|
||||
self.service = service
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
print("\n1. Add 2. List 3. Query 4. Stats 0. Exit")
|
||||
choice = input("Choice: ")
|
||||
if choice == "1":
|
||||
self._add()
|
||||
elif choice == "2":
|
||||
self._list()
|
||||
elif choice == "3":
|
||||
self._query()
|
||||
elif choice == "4":
|
||||
self._stats()
|
||||
elif choice == "0":
|
||||
break
|
||||
|
||||
def _add(self):
|
||||
student = Student(
|
||||
name=input("Name: "),
|
||||
id_card=input("ID Card: "),
|
||||
stu_id=input("Student ID: "),
|
||||
gender=input("Gender (True/False): ").lower() == 'true',
|
||||
height=int(input("Height: ")) if input("Height (optional): ") else None,
|
||||
weight=float(input("Weight: ")) if input("Weight (optional): ") else None
|
||||
)
|
||||
try:
|
||||
if self.service.add_student(student):
|
||||
print("Added successfully!")
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
def _list(self):
|
||||
for student in self.service.get_all():
|
||||
print(student.to_dict())
|
||||
|
||||
def _query(self):
|
||||
field = input("Field to query (name/class_name/major): ")
|
||||
value = input(f"{field} value: ")
|
||||
for student in self.service.query(**{field: value}):
|
||||
print(student.to_dict())
|
||||
|
||||
def _stats(self):
|
||||
field = input("Field for stats (class_name/major): ")
|
||||
for k, v in self.service.get_stats(field).items():
|
||||
print(f"{k}: {v}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
storage = input("Storage (csv/json/sqlite): ").lower()
|
||||
dal = {
|
||||
'csv': CSVStudentDAL('students.csv'),
|
||||
'json': JSONStudentDAL('students.json'),
|
||||
'sqlite': SQLiteStudentDAL('students.db')
|
||||
}.get(storage, SQLiteStudentDAL('students.db'))
|
||||
|
||||
service = StudentService(dal)
|
||||
StudentCLI(service).run()
|
Loading…
Reference in new issue