📜 Как показано в исходном материале
Исходное решение работает, но не содержит аннотаций типов. Обратите внимание, что внутри from_string переменная названа date и затеняет импортированный объект date из модуля datetime.
from datetime import datetime, date
class Student:
current_id = 1
def __init__(self, name, birth_date):
self.birth_date = datetime.strptime(birth_date, "%Y-%m-%d").date()
if not self.is_valid_birthdate():
raise ValueError("Student must be at least 16 years old.")
self.name = name
self.student_id = Student.current_id
Student.current_id += 1
def __str__(self):
return f"Student: {self.name}, birth_date: {self.birth_date}, ID: {self.student_id}"
def __repr__(self):
return f"Student: {self.name}, birth_date: {self.birth_date}, ID: {self.student_id}"
def show_info(self):
print(f"Student:\n\tName: {self.name}\n\tAge: {self.calculate_age()}\n\tID: {self.student_id}")
def calculate_age(self):
today = date.today()
return self.calculate_age_on(today)
def calculate_age_on(self, target_date):
age = target_date.year - self.birth_date.year
if (target_date.month, target_date.day) < (self.birth_date.month, self.birth_date.day):
age -= 1
return age
def is_valid_birthdate(self):
return self.calculate_age() >= 16
@classmethod
def from_string(cls, data):
name, date = map(str.strip, data.split(", "))
return cls(name, date)
@staticmethod
def filter_by_min_age(students, min_age):
return [s for s in students if s.calculate_age() >= min_age]
❌ Почему этот подход может быть не лучшим сейчас
- Без аннотаций типов IDE и линтеры не подскажут, какие аргументы ожидаются.
- Имя локальной переменной
dateвfrom_stringзатеняет импортdate, что путает при чтении и может привести к ошибкам. - Минимальный возраст захардкожен числом
16; лучше вынести в константу класса. __str__и__repr__в исходном материале совпадают, хотя семантически они разные.
✅ Рекомендуемый современный вариант
Современный Python позволяет добавить типы, убрать затенение имён и сделать различие между строковыми представлениями явным.
from datetime import datetime, date
class Student:
current_id: int = 1
MIN_AGE: int = 16
def __init__(self, name: str, birth_date: str) -> None:
self.birth_date = datetime.strptime(birth_date, "%Y-%m-%d").date()
if not self.is_valid_birthdate():
raise ValueError("Student must be at least 16 years old.")
self.name = name
self.student_id = Student.current_id
Student.current_id += 1
def __str__(self) -> str:
return f"Student: {self.name}, birth_date: {self.birth_date}, ID: {self.student_id}"
def __repr__(self) -> str:
return (
f"Student(name={self.name!r}, birth_date={self.birth_date!r}, "
f"student_id={self.student_id!r})"
)
def show_info(self) -> None:
print(
f"Student:\n\tName: {self.name}\n\tAge: {self.calculate_age()}\n\tID: {self.student_id}"
)
def calculate_age(self) -> int:
return self.calculate_age_on(date.today())
def calculate_age_on(self, target_date: date) -> int:
age = target_date.year - self.birth_date.year
if (target_date.month, target_date.day) < (self.birth_date.month, self.birth_date.day):
age -= 1
return age
def is_valid_birthdate(self) -> bool:
return self.calculate_age() >= self.MIN_AGE
@classmethod
def from_string(cls, data: str) -> "Student":
name, date_string = map(str.strip, data.split(","))
return cls(name, date_string)
@staticmethod
def filter_by_min_age(students: list["Student"], min_age: int) -> list["Student"]:
return [s for s in students if s.calculate_age() >= min_age]
Что улучшилось:
- Аннотации типов делают интерфейс класса понятным без чтения тела методов.
- Константа
MIN_AGEвынесена из кода и легко изменяется. - Переменная переименована в
date_string, чтобы не затенятьdate. __repr__возвращает строку, пригодную для воссоздания объекта.
Альтернатива: @dataclass
Если бы класс был «контейнером данных» без сложной валидации, можно было бы использовать @dataclass. Но здесь нужна проверка возраста, поэтому __init__ всё равно придётся писать явно через __post_init__.
from dataclasses import dataclass, field
from datetime import datetime, date
@dataclass
class Student:
name: str
birth_date: str
student_id: int = field(init=False)
current_id: ClassVar[int] = 1
MIN_AGE: ClassVar[int] = 16
def __post_init__(self) -> None:
self.birth_date = datetime.strptime(self.birth_date, "%Y-%m-%d").date()
if not self.is_valid_birthdate():
raise ValueError("Student must be at least 16 years old.")
self.student_id = Student.current_id
Student.current_id += 1
def calculate_age(self) -> int:
today = date.today()
age = today.year - self.birth_date.year
if (today.month, today.day) < (self.birth_date.month, self.birth_date.day):
age -= 1
return age
def is_valid_birthdate(self) -> bool:
return self.calculate_age() >= self.MIN_AGE
В учебном материале @dataclass не используется, поэтому для выполнения заданий применяйте обычный класс.
🕰️ Когда старый подход ещё можно встретить
В legacy-коде и старых учебных материалах классы часто пишут без аннотаций. Главное — понимать логику: datetime.strptime парсит строку, @classmethod получает cls, @staticmethod не получает self. При рефакторинге добавляйте типы постепенно и убирайте затенение имён.
@dataclass и ClassVar описаны в dataclasses и typing.ClassVar. Аннотации типов — в typing и PEP 484.