Нашли ошибку?
Скачать чистый
урок 1 из 3≈ 20 минупражнения: 0 из 3

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

Атрибут класса общий для всех экземпляров, атрибут экземпляра — у каждого свой. Константы класса, порядок поиска атрибута и список в атрибуте класса, который делят все.

Python запустится при первом выполнении

До сих пор все атрибуты записывались в __init__ через self — у каждого экземпляра свои. Но бывают данные, общие для всех экземпляров класса: валюта цен, предельная скидка, счётчик созданных заказов. Их хранят в самом классе.

Атрибут класса

Переменная, объявленная прямо в теле класса (не внутри метода), — атрибут класса (class attribute). Он один на весь класс, и его видно и через класс, и через любой экземпляр. Постоянные значения — константы класса — называют заглавными буквами:

[ ]Валюта — одна на всех
class Product:
    CURRENCY = "₽"  # атрибут класса: один на всех

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

    def label(self):
        return f"{self.name} — {self.price} {self.CURRENCY}"


latte = Product("Латте", 220)
tea = Product("Чай", 120)
print(Product.CURRENCY)
print(latte.CURRENCY, tea.CURRENCY)
print(latte.label())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
₽
₽ ₽
Латте — 220 ₽

Где Python ищет атрибут

Атрибуты экземпляра хранятся в словаре __dict__ самого экземпляра, атрибуты класса — в __dict__ класса. Когда вы пишете latte.CURRENCY, Python сначала ищет имя у экземпляра, а не найдя — у его класса (так ищутся обычные атрибуты; у свойств, о которых модуль 3, свои правила):

[ ]Словари экземпляра и класса
print(latte.__dict__)
print("CURRENCY" in latte.__dict__)
print("CURRENCY" in Product.__dict__)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
{'name': 'Латте', 'price': 220}
False
True

Поэтому метод может читать константу как self.CURRENCY: у экземпляра её нет, и Python найдёт её в классе.

[ ]Упражнение 1Предельная скидка решено

Допишите класс Product. Добавьте атрибут класса MAX_DISCOUNT = 30 — больше 30 % скидки кофейня не даёт. Метод discounted(percent) возвращает цену со скидкой, округлённую до целых, но если percent больше MAX_DISCOUNT, применяется MAX_DISCOUNT: Product("Латте", 200).discounted(50) — 140.

class Product:
    CURRENCY = "₽"
    # объявите здесь MAX_DISCOUNT

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

    def discounted(self, percent):
        ...

Запись атрибута через экземпляр

Читать атрибут класса через экземпляр можно, а вот запись через экземпляр работает иначе: latte.CURRENCY = "$" создаёт атрибут у экземпляра, и он закрывает собой атрибут класса — только для этого экземпляра. Изменить общее значение можно только через класс:

[ ]Запись через экземпляр и через класс
latte = Product("Латте", 220)
tea = Product("Чай", 120)
latte.CURRENCY = "$"  # атрибут появился у экземпляра latte
print(latte.CURRENCY, tea.CURRENCY, Product.CURRENCY)
Product.CURRENCY = "руб."  # меняем атрибут класса
print(latte.CURRENCY, tea.CURRENCY, Product.CURRENCY)
Product.CURRENCY = "₽"  # вернём как было
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
$ ₽ ₽
$ руб. руб.
Вопрос

Что напечатает этот код?

class Cup:
    size = "M"

a = Cup()
b = Cup()
a.size = "L"
Cup.size = "S"
print(a.size, b.size)

Из-за этого правила частая ошибка — счётчик в атрибуте класса, который увеличивают через self:

[ ]self.count += 1 не меняет счётчик класса
class Ticket:
    count = 0

    def __init__(self):
        self.count += 1  # ошибка: запись идёт в экземпляр


first = Ticket()
second = Ticket()
print(Ticket.count, first.count, second.count)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
0 1 1
[ ]Упражнение 2Номера заказов решено

Объявите класс Order. Атрибут класса created — сколько заказов создано, вначале 0. Каждый новый заказ увеличивает created на 1 и получает атрибут экземпляра number — свой порядковый номер: первый заказ — номер 1, второй — 2 и так далее.

class Order:
    created = 0

    def __init__(self):
        ...

Общий список — общая беда

Атрибут класса со списком тоже один на всех. Если экземпляры добавляют в него элементы, добавляют они в один и тот же список:

[ ]Две корзины с одним списком
class Basket:
    items = []  # ошибка: один список на все корзины

    def add(self, name):
        self.items.append(name)


anna = Basket()
boris = Basket()
anna.add("Латте")
boris.add("Чай")
print(anna.items)
print(boris.items)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
['Латте', 'Чай']
['Латте', 'Чай']
[ ]Упражнение 3Починить столики решено

В заготовке — класс Table (столик в зале) с той же ошибкой, что в демонстрации: заказы всех столиков попадают в один список. Исправьте класс так, чтобы у каждого столика был свой список orders, а метод order(drink) добавлял напиток только в заказы своего столика.

class Table:
    orders = []

    def __init__(self, number):
        self.number = number

    def order(self, drink):
        self.orders.append(drink)

Правило простое: атрибут класса — для того, что действительно общее и не меняется через экземпляры (константы, настройки). Всё, что у каждого объекта своё, — в __init__ через self.

Нашли ошибку?