Основы классов в Python

Основная идея
Класс — это расширяемый блок кода, который задаёт шаблон для объектов этого типа. Объект класса — конкретный экземпляр с собственными значениями атрибутов.
В Python всё — объект, и каждая сущность является экземпляром некоторого класса. Класс объединяет общие методы (поведения) и структуру данных (атрибуты) для всех его экземпляров.
Важно: в примере ниже класс Car моделирует сенсор, который добавляет штраф в 50 долларов, если скорость превышает порог 140 миль/ч (≈225 км/ч).
Исправленный и улучшенный пример класса Car
Ниже — корректный и аккуратно отформатированный пример класса на Python с русскими комментариями и базовой проверкой типов. Этот вариант безопаснее и удобнее для практики.
class Car:
"""Модель автомобиля.
Атрибуты экземпляра:
_car_name, _driver_name, _license_plate, _current_speed,
_speeding_charge, _colour
"""
SPEED_LIMIT_MPH = 140 # порог скорости в милях/ч
SPEEDING_FINE_USD = 50 # штраф в долларах
def __init__(self, car_name, driver_name, license_plate_number,
current_speed=0, speeding_charge=0, colour="Unknown"):
# Инициализируем атрибуты экземпляра
self._car_name = str(car_name)
self._driver_name = str(driver_name)
self._license_plate = str(license_plate_number)
self._current_speed = float(current_speed)
self._speeding_charge = float(speeding_charge)
self._colour = str(colour)
# Accessor-методы (геттеры)
def get_car_name(self):
return self._car_name
def get_driver_name(self):
return self._driver_name
def get_license_plate(self):
return self._license_plate
def get_current_speed(self):
return self._current_speed
def get_speeding_charge(self):
return self._speeding_charge
def get_colour(self):
return self._colour
# Mutator-методы (сеттеры и действия)
def set_driver(self, new_driver):
self._driver_name = str(new_driver)
def speeding_ticket(self, current_speed):
"""Проверяет скорость и добавляет штраф, если превышен лимит.
Возвращает True, если штраф добавлен, иначе False.
"""
try:
speed = float(current_speed)
except (TypeError, ValueError):
raise ValueError("current_speed должен быть числом")
self._current_speed = speed
if speed <= self.SPEED_LIMIT_MPH:
return False
else:
self._speeding_charge += self.SPEEDING_FINE_USD
return True
def make_payment(self, amount_paid):
"""Вычисляет оплату штрафа. amount_paid должен быть числом >= 0."""
try:
amount = float(amount_paid)
except (TypeError, ValueError):
raise ValueError("amount_paid должен быть числом")
if amount < 0:
raise ValueError("amount_paid не может быть отрицательным")
self._speeding_charge = max(0.0, self._speeding_charge - amount)Пример создания экземпляра:
# создаём экземпляр автомобиля
my_car = Car("Bugatti", "David Sasu", 90828, 0, 0, "Cherry Red")
# проверяем штраф за скорость
my_car.speeding_ticket(150) # True, добавлен штраф $50
print(my_car.get_speeding_charge()) # 50.0
# вносим оплату
my_car.make_payment(20)
print(my_car.get_speeding_charge()) # 30.0Пояснение ключевых концепций
Параметр self
self — это ссылка на конкретный экземпляр класса. В сигнатуре методов он передаётся явно, но при вызове метода на экземпляре программист его не указывает. Это связывает атрибуты и методы с текущим объектом.
Конструктор
Метод init создаёт и инициализирует новый объект. В нашем примере конструктор устанавливает шесть полей экземпляра: _car_name, _driver_name, _license_plate, _current_speed, _speeding_charge, _colour.
Геттеры и сеттеры
Геттеры (accessors) возвращают текущее состояние атрибутов. Сеттеры (mutators) и другие методы изменяют состояние объекта (например, set_driver, speeding_ticket, make_payment).
Инкапсуляция
Инкапсуляция означает, что внутреннее состояние объекта скрыто и изменяется только через публичные методы. В Python соглашение об имени с подчёркиванием (например, _driver_name) обозначает «внутреннее» поле.
Ошибки и проверка входных данных
Наш первоначальный пример был уязвим к ошибкам — функции не проверяли типы входных данных. В исправленном варианте мы:
- Приводим значения к нужным типам (str, float) в конструкторе;
- Бросаем понятные исключения ValueError при некорректных аргументах;
- Предотвращаем отрицательное значение штрафа при оплате.
Важно: всегда валидируйте входные данные в публичных методах, чтобы избежать неожиданных падений.
Когда такой класс может не подойти (примеры и ограничения)
- Многопоточная среда: при одновременном доступе к одному объекту нужна синхронизация.
- Более сложные правила начисления штрафа: если нужно учитывать гео-данные или временные окна — бизнес-логика усложнится.
- Международные единицы: если система должна работать с км/ч и милями/ч — нужно добавить параметр единиц.
Альтернативные подходы
- Использовать dataclass для автоматической генерации методов и упрощённой сериализации:
from dataclasses import dataclass
@dataclass
class CarData:
car_name: str
driver_name: str
license_plate: str
current_speed: float = 0.0
speeding_charge: float = 0.0
colour: str = "Unknown"- Разделять ответственность: выделить отдельный объект SpeedingPolicy для расчёта штрафов.
Умственная модель и эвристики
- Класс = шаблон; экземпляр = конкретный объект с собственными данными.
- Методы изменяют состояние; геттеры читают состояние.
- Инкапсуляция скрывает внутренние детали, одновременно давая контролируемый API.
Факт-бокс
- Порог в примере: 140 миль/ч (≈225 км/ч).
- Штраф в примере: $50.
- Поля экземпляра: 6.
Тесты и критерии приёмки
Критерии приёмки:
- При скорости ≤ 140 штраф не добавляется.
- При скорости > 140 штраф увеличивается ровно на 50.
- make_payment корректно уменьшает _speeding_charge и не делает значение отрицательным.
- Методы бросают ValueError при некорректных типах аргументов.
Минимальные тест-кейсы (pytest):
def test_no_ticket_below_limit():
c = Car("A", "B", 1, 100)
assert not c.speeding_ticket(100)
assert c.get_speeding_charge() == 0
def test_ticket_above_limit():
c = Car("A", "B", 1, 0)
assert c.speeding_ticket(141)
assert c.get_speeding_charge() == 50
def test_make_payment():
c = Car("A", "B", 1, 0, 100)
c.make_payment(30)
assert c.get_speeding_charge() == 70
def test_invalid_speed():
c = Car("A", "B", 1)
try:
c.speeding_ticket("fast")
assert False, "Expected ValueError"
except ValueError:
assert TrueРоль‑ориентированные чек‑листы
Для разработчика:
- Написать проверку типов и документацию.
- Добавить модульные тесты.
- Поддерживать инварианты (например, штраф ≥ 0).
Для ревьюера:
- Проверить обработку исключений и граничные случаи.
- Убедиться, что публичный API стабилен.
Для тестировщика:
- Тесты на граничные скорости: 140, 140.0, 140.1.
- Тесты на некорректные типы и негативные оплаты.
Краткий глоссарий
- Класс: шаблон для объектов.
- Экземпляр (объект): конкретный объект, созданный по шаблону класса.
- Метод: функция, связанная с классом/объектом.
- Атрибут: поле данных объекта.
- Инкапсуляция: скрытие внутреннего состояния за публичным API.
Заключение
Вы узнали, что класс в Python задаёт поведение и состояние объектов, как работает параметр self, зачем нужны геттеры и сеттеры и почему важно валидировать входные данные. Исправленный пример класса Car показывает практические приёмы: явное приведение типов, понятные исключения и простую бизнес‑логику для штрафов.
Короткий план следующего шага: реализуйте класс, напишите тесты из раздела «Тесты и критерии приёмки», затем выделите политику начисления штрафов в отдельный класс, если требования усложняются.
Важно: порог скорости указан в милях в исходном примере; при локализации интерфейса для пользователей добавьте возможность выбора единиц (mph / km/h).