В прошлом уроке баланс карты читался методом: card.balance(). Работает, но выглядит странно — баланс
скорее атрибут карты, чем действие. Python позволяет оставить внутренний _balance спрятанным и при этом
читать баланс как атрибут, без скобок. Для этого есть свойства (property).
@property
Строка @property перед методом — это декоратор (decorator): он меняет то, как работает метод под ним.
Метод с @property вызывается сам, когда атрибут читают, — скобки не нужны. Например, у заказа статус
хранится во внутреннем _status, а читается свойством status:
class Order:
def __init__(self, number):
self._number = number
self._status = "новый"
@property
def number(self):
return self._number
@property
def status(self):
return self._status
def pay(self):
if self._status == "оплачен":
return False # дважды не оплачивают
self._status = "оплачен"
return True
order = Order(17)
print(order.number, order.status) # без скобок
print(order.pay(), order.status)
print(order.pay(), order.status)17 новый True оплачен False оплачен
Снаружи order.status выглядит как обычный атрибут, но записать в него нельзя: у свойства есть только
«чтение» — геттер (getter). Так получается атрибут только для чтения:
order.status = "отменён"AttributeError: property 'status' of 'Order' object has no setter
Менять статус можно только методами заказа — например, pay(), который не даст оплатить заказ дважды.
Так же стоит поступить с балансом подарочной карты.
Допишите класс GiftCard: сделайте balance свойством, которое возвращает _balance. Методы pay и
top_up уже есть.
def balance(self): строка @property; внутри — return self._balance.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class GiftCard:
def __init__(self, amount):
self._balance = amount
def balance(self):
...
def pay(self, amount):
if amount > self._balance:
return False
self._balance -= amount
return True
def top_up(self, amount):
if amount <= 0:
return False
self._balance += amount
return TrueУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class GiftCard:
def __init__(self, amount):
self._balance = amount
@property
def balance(self):
return self._balance
def pay(self, amount):
if amount > self._balance:
return False
self._balance -= amount
return True
def top_up(self, amount):
if amount <= 0:
return False
self._balance += amount
return Trueorder.number()TypeError: 'int' object is not callable
Вычисляемый атрибут
Свойство не обязано возвращать сохранённый атрибут — оно может вычислять значение каждый раз, когда его
читают. Например, сумма корзины: её не нужно хранить и обновлять при каждом add, достаточно посчитать
по товарам:
Допишите класс Cart: свойство total возвращает сумму цен товаров, свойство count — число товаров
в корзине.
@property: return sum(p.price for p in self.items) и return len(self.items).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
class Cart:
def __init__(self):
self.items = []
def add(self, product):
self.items.append(product)
# свойства total и countУпражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
class Cart:
def __init__(self):
self.items = []
def add(self, product):
self.items.append(product)
@property
def total(self):
return sum(p.price for p in self.items)
@property
def count(self):
return len(self.items)Вычисляемое свойство всегда актуально: оно считается заново при каждом чтении. Значение, вычисленное
один раз в __init__, устареет, как только поменяются данные.
Допишите класс Drink: свойство size возвращает размер по объёму volume — до 250 мл включительно "S",
до 350 мл включительно "M", больше — "L". Если объём изменился, размер должен измениться вместе с ним.
if self.volume <= 250: return "S", затем проверка на 350 и return "L".Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Drink:
def __init__(self, name, volume):
self.name = name
self.volume = volume
@property
def size(self):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Drink:
def __init__(self, name, volume):
self.name = name
self.volume = volume
@property
def size(self):
if self.volume <= 250:
return "S"
if self.volume <= 350:
return "M"
return "L"Свойство или метод
Что из этого естественнее сделать свойством, а не методом?
pay и add меняют состояние и принимают аргументы — это действия, то есть методы.Правило выбора:
| Свойство | Метод |
|---|---|
характеристика объекта: balance, total, size |
действие: pay, add, remove |
| без параметров | с параметрами или без |
| ничего не меняет в объекте | может менять состояние |
| вычисляется быстро | может работать долго |
В следующем уроке у свойства появится и запись — с проверкой значения.