Классы и объекты

⏱ 35 мин · 🧩 заданий: 5

Зачем классы

Пока программа маленькая, хватает функций и словарей. Когда данных и действий над ними много, удобно объединить их в объекты: данные (атрибуты) + поведение (методы).

# без классов: данные и функции живут отдельно
user = {"name": "Анна", "balance": 100}
def deposit(user, amount):
    user["balance"] += amount

# с классом: данные и поведение вместе
class User:
    def __init__(self, name, balance=0):
        self.name = name
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

anna = User("Анна", 100)
anna.deposit(50)
print(anna.name, anna.balance)
  • Класс — шаблон, описание: какие атрибуты и методы будут у объектов.
  • Объект (экземпляр) — конкретная «вещь», созданная по шаблону: User("Анна").

init и self

class Point:
    def __init__(self, x, y):
        print(f"  __init__: self={self!r:.40}, x={x}, y={y}")
        self.x = x          # атрибут экземпляра
        self.y = y

    def distance_to_origin(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

p = Point(3, 4)
print(p.distance_to_origin())
print(Point.distance_to_origin(p))   # то же самое: p передаётся как self

При Point(3, 4) Python:

  1. создаёт пустой объект (Point.__new__);
  2. вызывает __init__(объект, 3, 4) — инициализатор заполняет атрибуты;
  3. возвращает объект.

self — обычный первый параметр, в который попадает экземпляр. Запись p.method(arg) превращается в Point.method(p, arg).

Пройдите пример 👣 Пошагово: визуализатор покажет объект Point и его атрибуты x, y.

Атрибуты экземпляра и класса

class Car:
    wheels = 4                    # атрибут класса: общий для всех

    def __init__(self, brand):
        self.brand = brand        # атрибут экземпляра: свой у каждого

a, b = Car("Lada"), Car("Kia")
print(a.wheels, b.wheels, Car.wheels)
Car.wheels = 6                    # меняем у класса — видно всем
print(a.wheels, b.wheels)
a.wheels = 3                      # присваивание через экземпляр создаёт СВОЙ атрибут
print(a.wheels, b.wheels, Car.wheels)
print(a.__dict__, b.__dict__)

Поиск атрибута

obj.attr ищется так: сначала в obj.__dict__ (атрибуты экземпляра), потом в классе, потом в родительских классах (по MRO). Присваивание obj.attr = value всегда пишет в obj.__dict__.

a.wheels → a.__dict__ {"brand": "Lada", "wheels": 3} ← найдено здесь b.wheels → b.__dict__ {"brand": "Kia"} → Car.__dict__ {"wheels": 6} ← здесь

Изменяемый атрибут класса

class Team: members = [] — список общий для всех команд. Изменяемые атрибуты создавайте в __init__. Подробнее — в уроке «Классические ловушки».

Методы

class Stack:
    def __init__(self):
        self._items = []

    def push(self, item):
        self._items.append(item)
        return self                 # возврат self позволяет цепочки вызовов

    def pop(self):
        if not self._items:
            raise IndexError("стек пуст")
        return self._items.pop()

    def peek(self):
        return self._items[-1] if self._items else None

    def __len__(self):
        return len(self._items)

s = Stack()
s.push(1).push(2).push(3)
print(s.pop(), s.peek(), len(s))

bound = s.push           # связанный метод: функция + запомненный self
print(bound, bound.__self__ is s)

repr и str

class Product:
    def __init__(self, name, price):
        self.name, self.price = name, price

    def __repr__(self):                       # для разработчика
        return f"Product({self.name!r}, {self.price})"

    def __str__(self):                        # для пользователя
        return f"{self.name} — {self.price} ₽"

p = Product("Кофе", 199)
print(p)                 # __str__
print(repr(p))           # __repr__
print([p])               # в контейнерах — __repr__
print(f"{p} / {p!r}")

Без __repr__ объект выводится как <__main__.Product object at 0x...>. Всегда определяйте __repr__ — это сильно упрощает отладку.

Динамические атрибуты

class Box:
    pass

b = Box()
b.color = "красный"           # атрибут можно добавить в любой момент
setattr(b, "size", 10)
print(b.__dict__, getattr(b, "color"), getattr(b, "weight", "нет"), hasattr(b, "size"))
del b.size
print(vars(b))

Это удобно, но атрибуты, созданные вне __init__, затрудняют чтение кода — объявляйте все атрибуты в __init__.

Четыре принципа ООП

Принцип Суть В Python
Инкапсуляция данные и методы вместе; детали скрыты за интерфейсом _protected, __private, property
Наследование новый класс на основе существующего class Admin(User)
Полиморфизм один интерфейс — разные реализации утиная типизация, переопределение методов
Абстракция выделение существенного, сокрытие лишнего ABC, Protocol, публичный API

Каждому посвящён отдельный урок модуля.

Итог

  • Класс — шаблон, объект — экземпляр; __init__ инициализирует атрибуты.
  • self — экземпляр, передаваемый первым аргументом; obj.m(x) ≡ Class.m(obj, x).
  • Атрибуты экземпляра — в obj.__dict__, атрибуты класса — общие; поиск: экземпляр → класс → родители.
  • __repr__ для разработчика, __str__ для пользователя.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Создайте класс BankAccount: - __init__(self, owner, balance=0) сохраняет владельца и баланс; - deposit(amount) увеличивает баланс; - withdraw(amount) уменьшает баланс, а если денег не хватает — бросает ValueError; - __repr__ возвращает BankAccount('Анна', 100).

Какие тесты проверяют код (3)
a = BankAccount("Анна"); a.deposit(150); a.withdraw(50); assert a.balance == 100
assert repr(BankAccount("Анна", 100)) == "BankAccount(\x27Анна\x27, 100)"
a = BankAccount("Боря", 10)
try:
    a.withdraw(20)
    ok = False
except ValueError:
    ok = True
assert ok and a.balance == 10, "При нехватке денег — ValueError, баланс не меняется"

Задание 2 Один вариант ответа

Что такое self?

Задание 3 Один вариант ответа

Что выведет код?

class Dog:
    species = "собака"

a, b = Dog(), Dog()
a.species = "волк"
print(a.species, b.species, Dog.species)
Задание 4 Программа на Python

Создайте класс Counter с атрибутом класса total — общим числом созданных счётчиков. Каждый новый экземпляр увеличивает Counter.total на 1 и получает свой номер self.number (первый созданный — 1, второй — 2 и т. д.).

Какие тесты проверяют код (2)
Counter.total = 0; a = Counter(); b = Counter(); c = Counter(); assert (a.number, b.number, c.number) == (1, 2, 3)
Counter.total = 0; Counter(); Counter(); assert Counter.total == 2
Задание 5 Один вариант ответа

Чем __repr__ отличается от __str__?

Войдите, чтобы отмечать пройденные уроки.