You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
4.0 KiB
113 lines
4.0 KiB
from datetime import date
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
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[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
|
|
|
|
if enrollment_date:
|
|
if isinstance(enrollment_date, str):
|
|
self.enrollment_date = date.fromisoformat(enrollment_date)
|
|
else:
|
|
self.enrollment_date = enrollment_date
|
|
else:
|
|
self.enrollment_date = None
|
|
|
|
self.class_name = class_name
|
|
self.major = major
|
|
|
|
@property
|
|
def birthday(self) -> Optional[date]:
|
|
if not self.id_card or len(self.id_card) != 18:
|
|
return None
|
|
try:
|
|
birth_str = self.id_card[6:14]
|
|
return date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
|
|
except:
|
|
return None
|
|
|
|
@property
|
|
def age(self) -> Optional[int]:
|
|
if not self.birthday:
|
|
return None
|
|
today = date.today()
|
|
age = today.year - self.birthday.year
|
|
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
|
|
age -= 1
|
|
return age
|
|
|
|
@property
|
|
def errors(self) -> Dict[str, str]:
|
|
errors = {}
|
|
id_card_err = self.__validate_id_card(self.id_card)
|
|
if id_card_err:
|
|
errors["id_card"] = id_card_err
|
|
if len(self.name) < 2 or len(self.name) > 20:
|
|
errors["name"] = "姓名长度需在2-20字符之间"
|
|
if self.height and not (50 <= self.height <= 250):
|
|
errors["height"] = "身高需在50-250cm之间"
|
|
if self.weight and not (5.0 <= self.weight <= 300.0):
|
|
errors["weight"] = "体重需在5-300kg之间"
|
|
if self.enrollment_date and self.enrollment_date > date.today():
|
|
errors["enrollment_date"] = "入学日期不能晚于当前日期"
|
|
return errors
|
|
|
|
@property
|
|
def is_valid(self) -> bool:
|
|
return not bool(self.errors)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
data = self.__dict__.copy()
|
|
if data.get("enrollment_date"):
|
|
data["enrollment_date"] = data["enrollment_date"].isoformat()
|
|
return data
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> 'Student':
|
|
if not isinstance(data, dict):
|
|
raise TypeError("输入必须为字典类型")
|
|
enrollment_date = data.get("enrollment_date")
|
|
if enrollment_date and isinstance(enrollment_date, str):
|
|
data["enrollment_date"] = date.fromisoformat(enrollment_date)
|
|
return cls(**data)
|
|
|
|
def __repr__(self) -> str:
|
|
attrs = ", ".join([
|
|
f"{k}='{v}'" if isinstance(v, str) else f"{k}={v}"
|
|
for k, v in self.__dict__.items()
|
|
])
|
|
return f"Student({attrs})"
|
|
|
|
@staticmethod
|
|
def get_properties() -> list:
|
|
return [k for k, v in vars(Student).items() if isinstance(v, property)]
|
|
|
|
@staticmethod
|
|
def __validate_id_card(id_card: str) -> Optional[str]:
|
|
if not id_card or len(id_card) != 18:
|
|
return "身份证号长度必须为18位"
|
|
if not id_card[:17].isdigit():
|
|
return "身份证号前17位必须为数字"
|
|
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
|
check_codes = "10X98765432"
|
|
sum_val = sum(int(id_card[i]) * weights[i] for i in range(17))
|
|
if id_card[17] != check_codes[sum_val % 11]:
|
|
return "身份证号校验码错误"
|
|
try:
|
|
birth_str = id_card[6:14]
|
|
date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
|
|
except:
|
|
return "身份证号中出生日期格式错误"
|
|
return None
|