До сих пор все атрибуты записывались в __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 найдёт её в классе.
Допишите класс Product. Добавьте атрибут класса MAX_DISCOUNT = 30 — больше 30 % скидки кофейня не даёт.
Метод discounted(percent) возвращает цену со скидкой, округлённую до целых, но если percent больше
MAX_DISCOUNT, применяется MAX_DISCOUNT: Product("Латте", 200).discounted(50) — 140.
MAX_DISCOUNT = 30.percent и self.MAX_DISCOUNT: встроенная min.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Product:
CURRENCY = "₽"
# объявите здесь MAX_DISCOUNT
def __init__(self, name, price):
self.name = name
self.price = price
def discounted(self, percent):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Product:
CURRENCY = "₽"
MAX_DISCOUNT = 30
def __init__(self, name, price):
self.name = name
self.price = price
def discounted(self, percent):
percent = min(percent, self.MAX_DISCOUNT)
return round(self.price * (100 - percent) / 100)Запись атрибута через экземпляр
Читать атрибут класса через экземпляр можно, а вот запись через экземпляр работает иначе:
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)a.size = "L" создал атрибут у экземпляра a, и он находится первым. У b своего size нет, поэтому b.size — это атрибут класса, а его изменили на "S".Из-за этого правила частая ошибка — счётчик в атрибуте класса, который увеличивают через self:
class Ticket:
count = 0
def __init__(self):
self.count += 1 # ошибка: запись идёт в экземпляр
first = Ticket()
second = Ticket()
print(Ticket.count, first.count, second.count)0 1 1
Объявите класс Order. Атрибут класса created — сколько заказов создано, вначале 0. Каждый новый заказ
увеличивает created на 1 и получает атрибут экземпляра number — свой порядковый номер: первый заказ —
номер 1, второй — 2 и так далее.
Order.created.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Order:
created = 0
def __init__(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Order:
created = 0
def __init__(self):
Order.created += 1
self.number = Order.createdОбщий список — общая беда
Атрибут класса со списком тоже один на всех. Если экземпляры добавляют в него элементы, добавляют они в один и тот же список:
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)['Латте', 'Чай'] ['Латте', 'Чай']
В заготовке — класс Table (столик в зале) с той же ошибкой, что в демонстрации: заказы всех столиков
попадают в один список. Исправьте класс так, чтобы у каждого столика был свой список orders, а метод
order(drink) добавлял напиток только в заказы своего столика.
Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Table:
orders = []
def __init__(self, number):
self.number = number
def order(self, drink):
self.orders.append(drink)Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Table:
def __init__(self, number):
self.number = number
self.orders = []
def order(self, drink):
self.orders.append(drink)Правило простое: атрибут класса — для того, что действительно общее и не меняется через экземпляры
(константы, настройки). Всё, что у каждого объекта своё, — в __init__ через self.