Перед тем как писать код, хороший разработчик внимательно читает ТЗ. Прочитай его целиком, прежде чем переходить к следующей части.
Нам поступил заказ на разработку консольного прототипа системы учёта для компьютерного клуба.
Администратор клуба хочет систему, в которой можно:
- регистрировать новых клиентов клуба (имя, никнейм, телефон);
- начинать игровую сессию клиента за компьютером с выбором тарифа;
- завершать игровую сессию — при завершении система должна автоматически посчитать и показать сумму к оплате;
- смотреть список всех клиентов, у которых сейчас идёт активная сессия;
- смотреть общее количество клиентов, когда-либо зарегистрированных в клубе — с момента самого первого запуска системы.
Клуб в системе может быть только один — не должно быть ситуации, когда в разных частях программы работают два независимых, несинхронизированных между собой каталога клиентов и сессий.
Программа работает в рамках одного запуска — сохранение данных между запусками в этой версии не требуется (это будет добавлено в следующей практике, когда будут пройдены темы, связанные с хранением данных).
- Никнейм клиента уникален — нельзя зарегистрировать двух клиентов с одинаковым никнеймом.
- Номер телефона клиента тоже уникален — нельзя зарегистрировать двух клиентов с одинаковым номером.
- Номер телефона должен соответствовать формату
+7XXXXXXXXXX(плюс, семёрка, затем ровно 10 цифр). - Тариф игровой сессии — строго одно из двух фиксированных значений:
standardилиvip. Любое другое значение — ошибка. - У одного клиента не может быть двух одновременно активных сессий.
- При завершении сессии стоимость рассчитывается автоматически на основе времени, прошедшего с начала сессии, и тарифа клиента. Администратор не вводит сумму вручную.
- Общее количество клиентов, зарегистрированных за всё время работы системы, должно накапливаться и не сбрасываться при создании новых объектов клуба внутри программы (даже если где-то в коде клуб «создаётся» повторно).
| Требование | Детали |
|---|---|
| Язык | Python 3.10+ |
| Хранилище данных | не требуется (всё хранится в оперативной памяти на время запуска) |
| Интерфейс | консоль (input/print) |
| Сторонние библиотеки | не использовать (только стандартная библиотека) |
Здесь описана точная архитектура, которую нужно реализовать. Отступать от неё без согласования с преподавателем нельзя — архитектура специально спроектирована так, чтобы закрепить пройденные темы Модуля 1.
computer_club/
│
├── models/
│ ├── __init__.py
│ ├── client.py # класс Client
│ └── session.py # класс Session
│
├── club/
│ ├── __init__.py
│ └── computer_club.py # класс ComputerClub (Singleton)
│
└── main.py # точка входа, консольное меню
Представляет клиента компьютерного клуба. На этом этапе курса приватность атрибутов ещё не изучалась, поэтому все атрибуты остаются публичными — тема инкапсуляции будет разобрана в следующем модуле.
Атрибуты:
| Атрибут | Тип | Описание |
|---|---|---|
name |
str |
имя клиента |
nickname |
str |
игровой никнейм клиента |
phone |
str |
номер телефона в формате +7XXXXXXXXXX |
Методы:
-
__init__(name, nickname, phone)— сохраняет данные клиента. Перед сохранением проверяет:name— непустая строка;nickname— непустая строка;phone— проходит проверкуis_valid_phone.
Если любая проверка не пройдена — выбрасывается
ValueErrorс понятным сообщением. -
is_valid_phone(phone) -> bool— статический метод. ВозвращаетTrue, если строка начинается с+7, а после неё идёт ровно 10 цифр, иначеFalse. Метод не должен использовать ниself, ниcls— это самостоятельная проверка, зависящая только от переданного значения. -
from_string(raw) -> Client— метод класса (альтернативный конструктор). Принимает строку вида"Иван;ivan_the_best;+79991234567"(значения разделены;), разбирает её на составляющие и возвращает новый объектClient. Если строка не разбивается ровно на три части — выбрасываетсяValueError. Метод должен создавать объект черезcls(...), а не через жёстко прописанное имя класса.
Представляет одну игровую сессию клиента за компьютером.
Атрибуты класса:
| Атрибут | Тип | Описание |
|---|---|---|
TARIFFS |
dict |
словарь допустимых тарифов вида {"standard": 100, "vip": 200} — цена указана в рублях за час |
Атрибуты объекта (задаются в __init__):
| Атрибут | Тип | Описание |
|---|---|---|
client |
Client |
клиент, для которого открыта сессия |
tariff |
str |
выбранный тариф ("standard" или "vip") |
start_time |
datetime |
момент начала сессии, устанавливается автоматически через datetime.now() |
Методы:
-
__init__(client, tariff)— проверяет, чтоtariffесть среди ключейTARIFFS(если нет —ValueErrorс перечислением допустимых тарифов), сохраняетclient,tariff, иstart_time = datetime.now(). -
__del__(self)— вызывается автоматически при удалении объекта сессии (то есть в момент, когда сессия завершается и убирается из активных). Должен:- вычислить, сколько времени прошло с
start_timeдо текущего момента (datetime.now()в момент вызова__del__); - рассчитать стоимость сессии на основе прошедшего времени и тарифа из
TARIFFS; - вывести в консоль сообщение вида:
[Чек] Сессия клиента ivan_the_best завершена. Время: 42.3 сек. К оплате: 1.17 руб.
Обрати внимание:
__del__в этой задаче — не основной механизм хранения данных (мы уже разбирали на уроке, что полагаться на__del__для критичных операций не стоит), а способ автоматически напечатать «чек» ровно в тот момент, когда сессия перестаёт существовать как активная. Реальный расчёт стоимости для полноценной системы стоило бы делать явным методом, а не полагаться только на__del__— но в рамках этой практики нам важно закрепить именно понимание того, когда и почему__del__вызывается. - вычислить, сколько времени прошло с
Главный класс системы. Должен гарантировать, что в программе существует только один экземпляр клуба — используй магический метод __new__ (паттерн Singleton, разобранный на Уроке 5).
Атрибуты класса:
| Атрибут | Тип | Описание |
|---|---|---|
__instance |
ComputerClub | None |
хранит единственный экземпляр класса (используется только внутри __new__) |
total_clients_ever_count |
int |
общее количество клиентов, когда-либо зарегистрированных за всё время работы программы |
Атрибуты объекта (задаются в __init__):
| Атрибут | Тип | Описание |
|---|---|---|
clients |
list[Client] |
список всех зарегистрированных клиентов |
active_sessions |
dict[str, Session] |
активные сессии: ключ — никнейм клиента, значение — объект Session |
Методы:
-
__new__(cls, *args, **kwargs)— реализует Singleton: если экземпляр ещё не создан — создаёт его черезsuper().__new__(cls)и сохраняет вcls.__instance; в любом случае возвращаетcls.__instance. -
__init__(self)— так как__init__вызывается при каждом обращении кComputerClub(), даже если объект уже существовал (это разбиралось на Уроке 5), метод должен содержать защиту от повторной инициализации: еслиclientsиactive_sessionsуже были созданы ранее — просто выйти из метода, ничего не пересоздавая. -
total_clients_ever(cls) -> int— метод класса. Возвращаетcls.total_clients_ever_count. -
register_client(self, name, nickname, phone) -> Client— регистрирует нового клиента:- проверяет через
find_client, что клиент с таким никнеймом ещё не зарегистрирован (иначеValueError); - проверяет, что ни у одного из уже зарегистрированных клиентов нет такого же номера телефона (иначе
ValueError); - создаёт объект
Client, добавляет его вclients; - увеличивает
total_clients_ever_countна 1; - возвращает созданный объект.
- проверяет через
-
register_client_from_string(self, raw) -> Client— создаёт клиента черезClient.from_string(raw), а затем регистрирует его точно так же, какregister_client(то есть с теми же проверками уникальности — переиспользуйregister_clientвнутри этого метода, не дублируя код). -
find_client(self, nickname) -> Client | None— ищет клиента по никнейму средиclients, возвращает найденный объект илиNone. -
start_session(self, nickname, tariff) -> Session— начинает сессию:- находит клиента через
find_client(если не найден —ValueError); - проверяет, что у клиента ещё нет активной сессии в
active_sessions(иначе —ValueError); - создаёт объект
Session, сохраняет вactive_sessionsпо ключу-никнейму, возвращает его.
- находит клиента через
-
close_session(self, nickname)— завершает сессию:- проверяет, что сессия для этого никнейма существует в
active_sessions(иначе —ValueError); - удаляет запись из
active_sessions(это должно быть единственным местом в программе, где хранилась ссылка на объектSession, поэтому удаление записи из словаря должно автоматически привести к вызовуSession.__del__).
- проверяет, что сессия для этого никнейма существует в
-
show_active_sessions(self)— выводит список всех активных сессий (никнейм, тариф, время начала). Если активных сессий нет — выводит сообщение об этом.
main.py
└── использует ComputerClub (Singleton, __new__)
├── хранит список Client
└── хранит словарь активных Session
└── Session ссылается на Client, для которого она открыта
Точка входа в приложение. Здесь находится только консольный интерфейс — никакой бизнес-логики (вся логика проверок должна быть внутри классов, а не в main.py).
=== Компьютерный клуб ===
1. Зарегистрировать клиента
2. Зарегистрировать клиента из строки (имя;никнейм;телефон)
3. Начать игровую сессию
4. Завершить игровую сессию
5. Показать активные сессии
6. Показать общее количество клиентов за всё время
0. Выйти
# main.py — структура (не финальный код, только скелет)
from club.computer_club import ComputerClub
def main():
club = ComputerClub()
while True:
# показать меню
# обработать выбор пользователя
# вызвать методы club.*, обернув в try/except ValueError
pass
if __name__ == "__main__":
main()Конкретная реализация всегда зависит от разработчика.
from club.computer_club import ComputerClub
def show_menu():
print("\n=== Компьютерный клуб ===")
print("1. Зарегистрировать клиента")
print("2. Зарегистрировать клиента из строки (имя;никнейм;телефон)")
print("3. Начать игровую сессию")
print("4. Завершить игровую сессию")
print("5. Показать активные сессии")
print("6. Показать общее количество клиентов за всё время")
print("0. Выйти")
return input("Ваш выбор: ").strip()
def main():
club = ComputerClub()
while True:
choice = show_menu()
if choice == "1":
name = input("Имя клиента: ").strip()
nickname = input("Никнейм: ").strip()
phone = input("Телефон (+7XXXXXXXXXX): ").strip()
try:
club.register_client(name, nickname, phone)
print(f"Клиент {nickname} успешно зарегистрирован")
except ValueError as e:
print(f"Ошибка: {e}")
elif choice == "2":
raw = input("Введите строку в формате 'имя;никнейм;телефон': ").strip()
try:
club.register_client_from_string(raw)
print("Клиент успешно зарегистрирован")
except ValueError as e:
print(f"Ошибка: {e}")
elif choice == "3":
nickname = input("Никнейм клиента: ").strip()
tariff = input("Тариф (standard/vip): ").strip()
try:
club.start_session(nickname, tariff)
print(f"Сессия для {nickname} начата")
except ValueError as e:
print(f"Ошибка: {e}")
elif choice == "4":
nickname = input("Никнейм клиента: ").strip()
try:
club.close_session(nickname)
except ValueError as e:
print(f"Ошибка: {e}")
elif choice == "5":
club.show_active_sessions()
elif choice == "6":
print(f"Всего клиентов за всё время: {ComputerClub.total_clients_ever()}")
elif choice == "0":
print("Работа программы завершена")
break
else:
print("Неверный выбор.")
if __name__ == "__main__":
main()Ориентировочное время выполнения: 2–3 академических часа.