From 2b8b420d8401886026a77228e22eef7f8f63189a Mon Sep 17 00:00:00 2001 From: HJW20 <1264519711@qq.com> Date: Wed, 25 Jun 2025 10:51:34 +0800 Subject: [PATCH] =?UTF-8?q?1=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 面向对象学生系统/Dal/__init__.py | 11 + 面向对象学生系统/Dal/add_students.py | 8 + .../Dal/check_students_exists.py | 2 + .../Dal/clear_all_students.py | 6 + .../Dal/delete_student.py | 7 + .../Dal/find_student_by_class_name.py | 3 + .../Dal/find_students_by_name.py | 3 + .../Dal/get_all_students.py | 2 + .../Dal/get_student_count.py | 2 + 面向对象学生系统/Dal/idal.py | 67 ++++ 面向对象学生系统/Dal/json_dal.py | 117 ++++++ 面向对象学生系统/Dal/load_students.py | 4 + 面向对象学生系统/Dal/save_students.py | 4 + 面向对象学生系统/Dal/studentDAL.py | 24 ++ .../Dal/update_student.py | 8 + .../Dal/update_student_partial.py | 18 + 面向对象学生系统/model/UI/__init__.py | 3 + 面向对象学生系统/model/UI/dal.py | 88 +++++ 面向对象学生系统/model/bll.py | 7 + 面向对象学生系统/model/test/Model.py | 45 +++ .../model/test/__init__.py | 354 ++++++++++++++++++ .../model/test/student.py | 49 +++ 面向对象学生系统/models/__init__.py | 0 面向对象学生系统/utils/__init__.py | 0 24 files changed, 832 insertions(+) create mode 100644 面向对象学生系统/Dal/__init__.py create mode 100644 面向对象学生系统/Dal/add_students.py create mode 100644 面向对象学生系统/Dal/check_students_exists.py create mode 100644 面向对象学生系统/Dal/clear_all_students.py create mode 100644 面向对象学生系统/Dal/delete_student.py create mode 100644 面向对象学生系统/Dal/find_student_by_class_name.py create mode 100644 面向对象学生系统/Dal/find_students_by_name.py create mode 100644 面向对象学生系统/Dal/get_all_students.py create mode 100644 面向对象学生系统/Dal/get_student_count.py create mode 100644 面向对象学生系统/Dal/idal.py create mode 100644 面向对象学生系统/Dal/json_dal.py create mode 100644 面向对象学生系统/Dal/load_students.py create mode 100644 面向对象学生系统/Dal/save_students.py create mode 100644 面向对象学生系统/Dal/studentDAL.py create mode 100644 面向对象学生系统/Dal/update_student.py create mode 100644 面向对象学生系统/Dal/update_student_partial.py create mode 100644 面向对象学生系统/model/UI/__init__.py create mode 100644 面向对象学生系统/model/UI/dal.py create mode 100644 面向对象学生系统/model/bll.py create mode 100644 面向对象学生系统/model/test/Model.py create mode 100644 面向对象学生系统/model/test/__init__.py create mode 100644 面向对象学生系统/model/test/student.py create mode 100644 面向对象学生系统/models/__init__.py create mode 100644 面向对象学生系统/utils/__init__.py diff --git a/面向对象学生系统/Dal/__init__.py b/面向对象学生系统/Dal/__init__.py new file mode 100644 index 0000000..4a1ef5f --- /dev/null +++ b/面向对象学生系统/Dal/__init__.py @@ -0,0 +1,11 @@ +class StudentDal: + def __init__(self,file_path:str): + self.file_path=file_path + self._ensure_file_exists() + + + +def _ensure_file_exists(self): + if not os.path.exists(self.file_path): + with open(self.file_path,mode='w',encoding='utf-8')as file: + json.dump([],file) diff --git a/面向对象学生系统/Dal/add_students.py b/面向对象学生系统/Dal/add_students.py new file mode 100644 index 0000000..9f9fba6 --- /dev/null +++ b/面向对象学生系统/Dal/add_students.py @@ -0,0 +1,8 @@ +def add_student(self,student:Student)-> bool: + students=self.load_students() + for existing_student in students: + if existing_student.sid==student.sid: + return False + students.append(student) + self.save_students(students) + return True \ No newline at end of file diff --git a/面向对象学生系统/Dal/check_students_exists.py b/面向对象学生系统/Dal/check_students_exists.py new file mode 100644 index 0000000..aa5d6f0 --- /dev/null +++ b/面向对象学生系统/Dal/check_students_exists.py @@ -0,0 +1,2 @@ +def check_students_exists(self,sid:str)-> bool: + return self.find_studemts_by_sid(sid) is not None \ No newline at end of file diff --git a/面向对象学生系统/Dal/clear_all_students.py b/面向对象学生系统/Dal/clear_all_students.py new file mode 100644 index 0000000..76c846b --- /dev/null +++ b/面向对象学生系统/Dal/clear_all_students.py @@ -0,0 +1,6 @@ +def clear_all_students(self)-> None: + self.save_students([]) + def get_student_count(self)->int: + return len(self.load_students()) + def check_student_exists(self,sid:str)-> bool: + return self.find_student_by_sid(sid) is not None diff --git a/面向对象学生系统/Dal/delete_student.py b/面向对象学生系统/Dal/delete_student.py new file mode 100644 index 0000000..b4f287b --- /dev/null +++ b/面向对象学生系统/Dal/delete_student.py @@ -0,0 +1,7 @@ +def delete_student(self,sid:str)-> bool: + students= self.load_students() + initial_length=len(students) + students=[student for student in students if student.sid!= sid] + if len(students) Lise[Student]: + students = self.load_students() + return[s for s in students if class_name.lower()in s.class_name.lower()] \ No newline at end of file diff --git a/面向对象学生系统/Dal/find_students_by_name.py b/面向对象学生系统/Dal/find_students_by_name.py new file mode 100644 index 0000000..2e1103d --- /dev/null +++ b/面向对象学生系统/Dal/find_students_by_name.py @@ -0,0 +1,3 @@ +def find_students_by_name(self,name:str)-> List[Student]: + students =self.load_students() + return [student for student in students if name.lower() in student.name.lower()] \ No newline at end of file diff --git a/面向对象学生系统/Dal/get_all_students.py b/面向对象学生系统/Dal/get_all_students.py new file mode 100644 index 0000000..8a807d9 --- /dev/null +++ b/面向对象学生系统/Dal/get_all_students.py @@ -0,0 +1,2 @@ +def get_all_students(self)-> List[Student]: + return self.load_students() \ No newline at end of file diff --git a/面向对象学生系统/Dal/get_student_count.py b/面向对象学生系统/Dal/get_student_count.py new file mode 100644 index 0000000..5bb1c1e --- /dev/null +++ b/面向对象学生系统/Dal/get_student_count.py @@ -0,0 +1,2 @@ +def get_student_count(self)-> int: + return len(self.load_students()) \ No newline at end of file diff --git a/面向对象学生系统/Dal/idal.py b/面向对象学生系统/Dal/idal.py new file mode 100644 index 0000000..65155ec --- /dev/null +++ b/面向对象学生系统/Dal/idal.py @@ -0,0 +1,67 @@ +from abc import ABC, abstractmethod +from typing import List, Optional +from models.student import Student + + +class IStudentDAL(ABC): + """学生信息数据访问层接口""" + + @abstractmethod + def add_student(self, student: Student) -> bool: + """添加学生""" + pass + + @abstractmethod + def delete_student(self, id_card: str) -> bool: + """删除学生""" + pass + + @abstractmethod + def update_student(self, student: Student) -> bool: + """更新学生信息""" + pass + + @abstractmethod + def get_by_id(self, id_card: str) -> Optional[Student]: + """根据身份证号获取学生""" + pass + + @abstractmethod + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + """根据学号获取学生""" + pass + + @abstractmethod + def get_all(self) -> List[Student]: + """获取所有学生""" + pass + + @abstractmethod + def get_by_name(self, name: str) -> List[Student]: + """按姓名查询(模糊)""" + pass + + @abstractmethod + def get_by_class(self, class_name: str) -> List[Student]: + """按班级查询(模糊)""" + pass + + @abstractmethod + def get_by_major(self, major: str) -> List[Student]: + """按专业查询(模糊)""" + pass + + @abstractmethod + def count_students(self) -> int: + """统计学生总数""" + pass + + @abstractmethod + def count_by_major(self) -> dict: + """按专业统计人数""" + pass + + @abstractmethod + def clear_all(self) -> bool: + """清空所有学生数据""" + pass \ No newline at end of file diff --git a/面向对象学生系统/Dal/json_dal.py b/面向对象学生系统/Dal/json_dal.py new file mode 100644 index 0000000..ab723b9 --- /dev/null +++ b/面向对象学生系统/Dal/json_dal.py @@ -0,0 +1,117 @@ +import json +import os +from typing import List, Optional +from models.student import Student +from dal.idal import IStudentDAL + + +class JsonStudentDAL(IStudentDAL): + """JSON存储实现""" + + def __init__(self, file_path: str = 'data/students.json'): + self.file_path = file_path + self._ensure_file_exists() + + def _ensure_file_exists(self): + + os.makedirs(os.path.dirname(self.file_path), exist_ok=True) + if not os.path.exists(self.file_path): + with open(self.file_path, 'w', encoding='utf-8') as f: + json.dump([], f) + + def _load_data(self) -> List[dict]: + """加载JSON数据""" + with open(self.file_path, 'r', encoding='utf-8') as f: + return json.load(f) + + def _save_data(self, data: List[dict]): + """保存数据到JSON文件""" + with open(self.file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + + def add_student(self, student: Student) -> bool: + data = self._load_data() + + # 检查学号和身份证号是否已存在 + for item in data: + if item['id_card'] == student.id_card or item['stu_id'] == student.stu_id: + return False + + data.append(student.to_dict()) + self._save_data(data) + return True + + def delete_student(self, id_card: str) -> bool: + data = self._load_data() + original_length = len(data) + + data = [item for item in data if item['id_card'] != id_card] + + if len(data) != original_length: + self._save_data(data) + return True + return False + + def update_student(self, student: Student) -> bool: + data = self._load_data() + updated = False + + for i, item in enumerate(data): + if item['id_card'] == student.id_card: + data[i] = student.to_dict() + updated = True + break + + if updated: + self._save_data(data) + return updated + + def get_by_id(self, id_card: str) -> Optional[Student]: + data = self._load_data() + for item in data: + if item['id_card'] == id_card: + return Student.from_dict(item) + return None + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + data = self._load_data() + for item in data: + if item['stu_id'] == stu_id: + return Student.from_dict(item) + return None + + def get_all(self) -> List[Student]: + data = self._load_data() + return [Student.from_dict(item) for item in data] + + def get_by_name(self, name: str) -> List[Student]: + data = self._load_data() + return [Student.from_dict(item) for item in data if name.lower() in item['name'].lower()] + + def get_by_class(self, class_name: str) -> List[Student]: + data = self._load_data() + return [Student.from_dict(item) for item in data + if item['class_name'] and class_name.lower() in item['class_name'].lower()] + + def get_by_major(self, major: str) -> List[Student]: + data = self._load_data() + return [Student.from_dict(item) for item in data + if item['major'] and major.lower() in item['major'].lower()] + + def count_students(self) -> int: + data = self._load_data() + return len(data) + + def count_by_major(self) -> dict: + data = self._load_data() + majors = {} + + for item in data: + major = item.get('major', '未指定') + majors[major] = majors.get(major, 0) + 1 + + return majors + + def clear_all(self) -> bool: + self._save_data([]) + return True \ No newline at end of file diff --git a/面向对象学生系统/Dal/load_students.py b/面向对象学生系统/Dal/load_students.py new file mode 100644 index 0000000..40ee6d9 --- /dev/null +++ b/面向对象学生系统/Dal/load_students.py @@ -0,0 +1,4 @@ +def load_students(self)-> List[Student]: + with open(self.file_path,mode='r',encoding='utf-8')as file: + student_dicts=json.load(file) + return[Student.from_dict(student_dict) for student_dict in student_dicts] \ No newline at end of file diff --git a/面向对象学生系统/Dal/save_students.py b/面向对象学生系统/Dal/save_students.py new file mode 100644 index 0000000..9c55753 --- /dev/null +++ b/面向对象学生系统/Dal/save_students.py @@ -0,0 +1,4 @@ +def save_students(self,students:List[Student])-> None: + student_dicts=[student.to_dict() for student in students] + with open(self.file_path,mode="w",encoding="utf-8") as file: + json.dump(student_dicts,file,ensure_ascii=False,indent=4) \ No newline at end of file diff --git a/面向对象学生系统/Dal/studentDAL.py b/面向对象学生系统/Dal/studentDAL.py new file mode 100644 index 0000000..c452873 --- /dev/null +++ b/面向对象学生系统/Dal/studentDAL.py @@ -0,0 +1,24 @@ +import json + +from typing_extensions import Optional +from xlwings.pro.reports.filters import height + +from 面向对象学生系统.model.test.student import Student +class StudentDAL: + def __init__(self,file_path:str): + self.file_path=file_path + self._ensure_file_exists() +def _ensure_file_exist(self): + if not os.path.exists(self.file_path): + with open(self,file_path,mode='w',encoding='utf-8') as file: + json.dump([],file) + + + + + + + + + + diff --git a/面向对象学生系统/Dal/update_student.py b/面向对象学生系统/Dal/update_student.py new file mode 100644 index 0000000..4fe1f2f --- /dev/null +++ b/面向对象学生系统/Dal/update_student.py @@ -0,0 +1,8 @@ +def update_student(self,sid:str,updated_student:Student)-> bool: + students =self.load_students() + for i, student in enumerate(students): + if student.sid== sid: + student[i]=upadted_student + self.save_students(students) + return True + return False \ No newline at end of file diff --git a/面向对象学生系统/Dal/update_student_partial.py b/面向对象学生系统/Dal/update_student_partial.py new file mode 100644 index 0000000..ebacf1b --- /dev/null +++ b/面向对象学生系统/Dal/update_student_partial.py @@ -0,0 +1,18 @@ +def update_student_partial(self, sid: str, name: Optional[str] = None, height:Optional[float]=None, birth_date: Optional[date] = None, enrollment_date:Optional[date] = None, class_name:Optional[ + str] = None)->bool: +students = self.load_students() +for i, student in enumerate(students): + if students.sid == sid: + if name is not None: + students[i].name = name + if height is not None: + students[i].height = height + if birth_date is not None: + students[i].birth_date = birth_date + if enrollemnt_date is not None: + students[i].enrollemnt_date = enrollemnt_date + if class_name is not None: + students[i].class_name = class_name + self.save_students(students) + return True +return False \ No newline at end of file diff --git a/面向对象学生系统/model/UI/__init__.py b/面向对象学生系统/model/UI/__init__.py new file mode 100644 index 0000000..8fa2cbb --- /dev/null +++ b/面向对象学生系统/model/UI/__init__.py @@ -0,0 +1,3 @@ +class StudentTUI: + def __init__(self,bll:StudentBLL): + self.bll=bll \ No newline at end of file diff --git a/面向对象学生系统/model/UI/dal.py b/面向对象学生系统/model/UI/dal.py new file mode 100644 index 0000000..b6d8590 --- /dev/null +++ b/面向对象学生系统/model/UI/dal.py @@ -0,0 +1,88 @@ +import json + +from typing_extensions import Optional +from xlwings.pro.reports.filters import height + +from 面向对象学生系统.model.test.student import Student +class StudentDAL: + def __init__(self,file_path:str): + self.file_path=file_path + self._ensure_file_exists() +def _ensure_file_exist(self): + if not os.path.exists(self.file_path): + with open(self,file_path,mode='w',encoding='utf-8') as file: + json.dump([],file) +def save_students(self,students:List[Student])-> None: + student_dicts=[student.to_dict() for student in students] + with open(self.file_path,mode="w",encoding="utf-8") as file: + json.dump(student_dicts,file,ensure_ascii=False,indent=4) +def load_students(self)-> List[Student]: + with open(self.file_path,mode='r',encoding='utf-8')as file: + student_dicts=json.load(file) + return[Student.from_dict(student_dict) for student_dict in student_dicts] +def add_student(self,student:Student)-> bool: + students=self.load_students() + for existing_student in students: + if existing_student.sid==student.sid: + return False + students.append(student) + self.save_students(students) + return True +def find_student_by_sid(self,sid:str)-> Optional[Student]: + students =self.load_students() + for student in students: + if student.sid==sid: + return student + return None +def find_students_by_name(self,name:str)-> List[Student]: + students =self.load_students() + return [student for student in students if name.lower() in student.name.lower()] +def find_student_by_class_name(slef,class_nmae:str)-> Lise[Student]: + students = self.load_students() + return[s for s in students if class_name.lower()in s.class_name.lower()] +def get_all_students(self)-> List[Student]: + return self.load_students() +def update_student(self,sid:str,upadted_student:Student)-> bool: + students =self.load_students() + for i, student in enumerate(students): + if student.sid== sid: + student[i]=upadted_student + self.save_students(students) + return True + return False +def update_student_partial(self,sid:str,name:Optional[str]=None,height=Optional[float]=None,birth_date:Optional[date]=None,enrollment_date:Optional[date]=None,class_name:Optional[str]=None)->bool: + students= self.load_students() + for i,student in enumerate(students): + if students.sid==sid: + + if name is not None: + students[i].name=name + if height is not None: + students[i].height=height + if birth_date is not None: + students[i].birth_date=birth_date + if enrollemnt_date is not None: + students[i].enrollemnt_date=enrollemnt_date + if class_name is not None: + students[i].class_name=class_name + self.save_students(students) + return True + return False +def delete_student(self,sid:str)-> bool: + students= self.load_students() + initial_length=len(students) + students=[student for student in students if student.sid!= sid] + if len(students) None: + self.save_students([]) + def get_student_count(self)->int: + return len(self.load_students()) + def check_student_exists(self,sid:str)-> bool: + return self.find_student_by_sid(sid) is not None + + + + + diff --git a/面向对象学生系统/model/bll.py b/面向对象学生系统/model/bll.py new file mode 100644 index 0000000..e76d178 --- /dev/null +++ b/面向对象学生系统/model/bll.py @@ -0,0 +1,7 @@ +class StudentBll: + def __init__(self,dal:StudentDal): + self.dal=dal +def delete_student(self,sid:str)-> bool: + if not self.dal.check_student_exists(sid): + raise ValueError(f"学生 ID {sid}不存在。") + return self.dal.delete_students(sid) diff --git a/面向对象学生系统/model/test/Model.py b/面向对象学生系统/model/test/Model.py new file mode 100644 index 0000000..2cc642d --- /dev/null +++ b/面向对象学生系统/model/test/Model.py @@ -0,0 +1,45 @@ +class Student: + def __init__(self,sid:str,name:str,height:int,birth_date:date|str,enrollment_date:date| str,class_name:str): + self.sid=sid + self.name=name.strip() + self.height=height + self.birth_date =birth_date if isinstance(birth_date,date) else date.fromisoformat(birth_date) + self.enrollment_date=enrollment_date if isinstance(enrollment_date,date)else date.fromisoformat(enrollment_date) + self.class_name=class_name + self._validation_errors=[] + self._validation_height() + self._validation_date() + self._validation_name() +def _validate_height(self)-> None: + if not (50<=self.height<=250): + self._validation_errors.append(f"身高{self.height}cm超出合理范围(50-250厘米)") +def _validate_name(self)-> None: + if not(2<=len(self.name)<=20): + self._validation_errors.append(f"姓名包含不可打印字符") +def _validate_date(self)-> None: + today =date.today() + if self.birth_date>today: + self._validation_errors.append("出生日期不能在未来 ") + if self.enrollment_date >today: + self._validation_errors.append(" 入学日期不能在未来 ") + if self.birth_date>self.enrollment_date: + self._validation_errors.append("入学日期不能早于出生日期") +@property +def is_valid(self)->bool: + return len(self._validtion_errors)==0 +def get_errors(self)-> list[str]: + return self.validtion_errors.copy() +def __eq__(self,other)->bool: + if not isinstance(other,Student): + return NotImplemented + return(self.sid==other.sid and self.name==other.name and self.height== other.height and self.birth_date==other.birth_date and self.enrollment_date==other.enrollment_date and self.class_name==other.class_name) +def to_dict(self)-> dict: + return{'sid':self.sid,'name':self.name,'height':self.height,'birth_date':self.birth_date.isoformat(),'enrollment_date':self.enrollment_date_isoformat().'class_name':self.class_name} +@classmethod +def from_dict(cls,date:dict)->'Student': + birth_date =bir_d if isinstance(bir_d:=data['birth_date'],date)else date.fromisoformat(bir_d) + enrollment_date=enr_d if isinstance(enr_d:=data['enrollment_date'],data)else date.fromisoformat(enr_d) + return cls(sid=data['sid'],name=data['name'].strip(),height=data['height'],birth_date=birth_date,enrollment_date=enrollment_date,class_name=data['class_name']) +def __repr__(self)->str: + return(f"{self.__class__.__name__}("f"sid='{self.sid}',"f"name='{self.name}',"f"height={self.height},"f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day})," + f") \ No newline at end of file diff --git a/面向对象学生系统/model/test/__init__.py b/面向对象学生系统/model/test/__init__.py new file mode 100644 index 0000000..351cab6 --- /dev/null +++ b/面向对象学生系统/model/test/__init__.py @@ -0,0 +1,354 @@ +import re +from datetime import date, datetime +from typing import Dict, Optional, Union, List + + +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: Union[date, str, None] = None, + class_name: Optional[str] = None, + major: Optional[str] = None + ): + """ + 初始化学生对象 + + :param name: 学生姓名,2-20个字符 + :param id_card: 身份证号,18位含正确校验码 + :param stu_id: 学号,非空 + :param gender: 性别,True(男)/False(女)/None(未知) + :param height: 身高(cm),50-250之间 + :param weight: 体重(kg),5-300之间,保留1位小数 + :param enrollment_date: 入学日期,date对象或YYYY-MM-DD格式字符串 + :param class_name: 班级名称 + :param major: 专业名称 + """ + self.name = name + self.id_card = id_card + self.stu_id = stu_id + self.gender = gender + self.height = height + self.weight = weight + self.class_name = class_name + self.major = major + + # 处理入学日期 + if isinstance(enrollment_date, str): + try: + self.enrollment_date = date.fromisoformat(enrollment_date) + except ValueError: + self.enrollment_date = None + else: + self.enrollment_date = enrollment_date + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def id_card(self) -> str: + return self._id_card + + @id_card.setter + def id_card(self, value: str): + self._id_card = value + + @property + def stu_id(self) -> str: + return self._stu_id + + @stu_id.setter + def stu_id(self, value: str): + self._stu_id = value + + @property + def gender(self) -> Optional[bool]: + return self._gender + + @gender.setter + def gender(self, value: Optional[bool]): + self._gender = value + + @property + def height(self) -> Optional[int]: + return self._height + + @height.setter + def height(self, value: Optional[int]): + self._height = value + + @property + def weight(self) -> Optional[float]: + return self._weight + + @weight.setter + def weight(self, value: Optional[float]): + self._weight = value + + @property + def enrollment_date(self) -> Optional[date]: + return self._enrollment_date + + @enrollment_date.setter + def enrollment_date(self, value: Optional[Union[date, str]]): + if isinstance(value, str): + try: + self._enrollment_date = date.fromisoformat(value) + except ValueError: + self._enrollment_date = None + else: + self._enrollment_date = value + + @property + def class_name(self) -> Optional[str]: + return self._class_name + + @class_name.setter + def class_name(self, value: Optional[str]): + self._class_name = value + + @property + def major(self) -> Optional[str]: + return self._major + + @major.setter + def major(self, value: Optional[str]): + self._major = value + + # 只读属性 + @property + def birthday(self) -> Optional[date]: + """从身份证号中提取出生日期""" + if not self.id_card or len(self.id_card) != 18: + return None + + birth_date_str = self.id_card[6:14] + try: + return date( + year=int(birth_date_str[0:4]), + month=int(birth_date_str[4:6]), + day=int(birth_date_str[6:8]) + ) + except ValueError: + return None + + @property + def age(self) -> Optional[int]: + """计算年龄""" + if not self.birthday: + return None + + today = date.today() + age = today.year - self.birthday.year + + # 如果生日还没到,年龄减1 + if (today.month, today.day) < (self.birthday.month, self.birthday.day): + age -= 1 + + return age + + @property + def errors(self) -> Dict[str, str]: + """返回所有校验错误信息""" + errors = {} + + # 校验姓名 + if not isinstance(self.name, str) or len(self.name) < 2 or len(self.name) > 20: + errors['name'] = "姓名必须为2-20个字符的字符串" + + # 校验学号 + if not isinstance(self.stu_id, str) or not self.stu_id: + errors['stu_id'] = "学号不能为空" + + # 校验身份证号 + id_card_errors = self.__validate_id_card() + if id_card_errors: + errors['id_card'] = id_card_errors + + # 校验身高 + if self.height is not None: + if not isinstance(self.height, int) or self.height < 50 or self.height > 250: + errors['height'] = "身高必须在50-250厘米之间" + + # 校验体重 + if self.weight is not None: + if not isinstance(self.weight, (int, float)) or self.weight < 5 or self.weight > 300: + errors['weight'] = "体重必须在5-300千克之间" + elif isinstance(self.weight, float) and len(str(self.weight).split('.')[1]) > 1: + errors['weight'] = "体重最多保留一位小数" + + # 校验入学日期 + if self.enrollment_date is not None: + if not isinstance(self.enrollment_date, date): + errors['enrollment_date'] = "入学日期格式无效" + elif self.enrollment_date > date.today(): + errors['enrollment_date'] = "入学日期不能晚于当前日期" + + return errors + + @property + def is_valid(self) -> bool: + """检查学生信息是否全部有效""" + return not bool(self.errors) + + def __validate_id_card(self) -> Optional[str]: + """验证身份证号有效性""" + if not isinstance(self.id_card, str): + return "身份证号必须为字符串" + + id_card = self.id_card.upper() + + # 长度校验 + if len(id_card) != 18: + return "身份证号长度必须为18位" + + # 前17位必须为数字 + if not id_card[:17].isdigit(): + return "身份证号前17位必须为数字" + + # 最后一位可以是数字或X + last_char = id_card[17] + if not (last_char.isdigit() or last_char == 'X'): + return "身份证号最后一位必须是数字或X" + + # 校验码验证 + check_code = self.__get_check_code(id_card[:17]) + if check_code != last_char: + return f"身份证号校验位错误,应为{check_code}" + + # 出生日期验证 + birth_date_str = id_card[6:14] + if not self.__validate_date(birth_date_str): + return "身份证号中的出生日期无效" + + return None + + @staticmethod + def __get_check_code(first_17: str) -> str: + """计算身份证校验码""" + if len(first_17) != 17 or not first_17.isdigit(): + raise ValueError("输入必须是17位数字") + + # 加权因子 + weights = [2 ** (17 - i) % 11 for i in range(17)] + + # 计算加权和 + total = sum(int(digit) * weight for digit, weight in zip(first_17, weights)) + + # 计算校验码 + check_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8', + 5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'} + return check_map[total % 11] + + @staticmethod + def __validate_date(date_str: str) -> bool: + """验证日期字符串(YYYYMMDD)是否有效""" + if len(date_str) != 8 or not date_str.isdigit(): + return False + + year = int(date_str[0:4]) + month = int(date_str[4:6]) + day = int(date_str[6:8]) + + # 简单验证年份范围 + if year < 1900 or year > date.today().year: + return False + + # 验证月份 + if month < 1 or month > 12: + return False + + # 验证日期 + try: + date(year=year, month=month, day=day) + return True + except ValueError: + return False + + def to_dict(self) -> Dict: + """将学生对象转换为字典""" + data = { + 'name': self.name, + 'id_card': self.id_card, + 'stu_id': self.stu_id, + 'gender': self.gender, + 'height': self.height, + 'weight': self.weight, + 'class_name': self.class_name, + 'major': self.major + } + + # 处理入学日期 + if self.enrollment_date: + data['enrollment_date'] = self.enrollment_date.isoformat() + else: + data['enrollment_date'] = None + + return data + + @classmethod + def from_dict(cls, data: Dict) -> 'Student': + """从字典创建学生对象""" + if not isinstance(data, dict): + raise TypeError("输入数据必须是字典类型") + + # 处理入学日期 + enrollment_date = data.get('enrollment_date') + if isinstance(enrollment_date, str): + try: + enrollment_date = date.fromisoformat(enrollment_date) + except ValueError: + enrollment_date = None + + return cls( + name=data.get('name'), + id_card=data.get('id_card'), + stu_id=data.get('stu_id'), + gender=data.get('gender'), + height=data.get('height'), + weight=data.get('weight'), + enrollment_date=enrollment_date, + class_name=data.get('class_name'), + major=data.get('major') + ) + + def __repr__(self) -> str: + """返回对象的字符串表示""" + attrs = [] + for key, value in vars(self).items(): + # 去掉下划线前缀 + clean_key = key.lstrip('_') + + if value is None: + attrs.append(f"{clean_key}=None") + elif isinstance(value, str): + attrs.append(f"{clean_key}='{value}'") + elif isinstance(value, date): + attrs.append(f"{clean_key}=date.fromisoformat('{value.isoformat()}')") + else: + attrs.append(f"{clean_key}={value}") + + return f"Student({', '.join(attrs)})" + + @classmethod + def get_properties(cls) -> List[str]: + """获取使用@property装饰的属性列表""" + properties = [] + for name, value in vars(cls).items(): + if isinstance(value, property): + properties.append(name) + return properties \ No newline at end of file diff --git a/面向对象学生系统/model/test/student.py b/面向对象学生系统/model/test/student.py new file mode 100644 index 0000000..415359d --- /dev/null +++ b/面向对象学生系统/model/test/student.py @@ -0,0 +1,49 @@ +from datetime import date +class Student: + def __init__(self,sid:str,name:str,height:int,birth_date:date|str,enrollment_date:date| str,class_name:str): + self.sid=sid + self.name=name.strip() + self.height=height + self.birth_date =birth_date if isinstance(birth_date,date) else date.fromisoformat(birth_date) + self.enrollment_date=enrollment_date if isinstance(enrollment_date,date)else date.fromisoformat(enrollment_date) + self.class_name=class_name + self._validation_errors=[] + self._validation_height() + self._validation_date() + self._validation_name() + def _validate_height(self)-> None: + if not(50<=self.height<=250): + self._validation_errors.append(f"身高{self.height}cm超出合理范围(50-250厘米)") + def _validate_name(self)-> None: + if not(2<=len(self.name)<=20): + self._validation_errors.append("姓名长度需在2-20个字符之间") + if not self.name.isprintable(): + self._validation_errors.append("姓名包含不可打印字符") + def _validate_date(self)-> None: + today=date.today() + if self.birth_date>today: + self._validation_errors.append("出生日期不能在未来") + if self.enrollment_date>today: + self._validation_errors.append("入学日期不能在未来") + if self.birth_date>self.enrollment_date: + self._validation_errors.append("入学日期不能早于出生日期") +@property +def is_valid(self)-> bool: + return len(self._validation_errors)==0 +def get_errors(self)-> list[str]: + return self.validation_error.copy() + +def __eq__(self,other)->bool: + if not isinstance(other,Student): + return NotImplemented + return(self.sid==other.sid and self.name==other.name and self.height== other.height and self.birth_date==other.birth_date and self.enrollment_date==other.enrollment_date and self.class_name==other.class_na) +def to_dict(self)-> dict: + return{'sid':self.sid,'name':self.name,'height':self.height,'birth_date':self.birth_date.isoformat(),'enrollment_date':self.enrollment_date_isoformat().'class_name':self.class_name} +@classmethod +def from_dict(cls,date:dict)->'Student': + birth_date =bir_d if isinstance(bir_d:=data['birth_date'],date)else date.fromisoformat(bir_d) + enrollment_date=enr_d if isinstance(enr_d:=data['enrollment_date'],data)else date.fromisoformat(enr_d) + return cla(sid=data['sid'],name=data['name'].strip(),height=data['height'],birth_date=birth_date,enrollment_date=enrollment_date,class_name=data['class_name']) +def __repr__(self)->str: + return(f"{self.__class__.__name__}("f"sid='{self.sid}',"f"name='{self.name}',"f"height={self.height},"f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day})," + f") diff --git a/面向对象学生系统/models/__init__.py b/面向对象学生系统/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/面向对象学生系统/utils/__init__.py b/面向对象学生系统/utils/__init__.py new file mode 100644 index 0000000..e69de29