Свойство только для чтения не даёт изменить атрибут через публичный интерфейс. Но цену товара менять нужно — просто не на любое значение: отрицательная цена — ошибка в программе, и лучше узнать о ней сразу. Для этого у свойства бывает вторая половина — сеттер (setter): метод, который вызывается при записи в свойство.
Сеттер
Сеттер объявляется вторым методом с тем же именем и декоратором @имя.setter. Он получает новое значение,
проверяет его и записывает во внутренний атрибут:
class Cup:
def __init__(self, volume):
self._volume = volume
@property
def volume(self):
return self._volume
@volume.setter
def volume(self, value):
print(f"сеттер получил {value}")
if value < 50:
print("слишком мало — объём не изменён")
return
self._volume = value
cup = Cup(300)
cup.volume = 400 # вызывается сеттер
print(cup.volume)
cup.volume = 10
print(cup.volume)сеттер получил 400 400 сеттер получил 10 слишком мало — объём не изменён 400
raise: сообщить об ошибке
Что делать, если значение неверное? Молча проигнорировать — плохо: программа продолжит работать с неверными
данными, и ошибка всплывёт далеко отсюда. Правильно — выбросить исключение (raise) — так же, как это
делает Python, когда вы пишете int("сто"):
class Cup:
def __init__(self, volume):
self._volume = volume
@property
def volume(self):
return self._volume
@volume.setter
def volume(self, value):
if value < 50:
raise ValueError(f"объём {value} мл — меньше 50 мл не бывает")
self._volume = value
cup = Cup(300)
cup.volume = 10ValueError: объём 10 мл — меньше 50 мл не бывает
raise ValueError("…") сразу прерывает метод: строки ниже не выполняются, атрибут не меняется, а вызвавший
код получает исключение. ValueError — встроенное исключение для «значение подходящего типа, но
недопустимое». Сообщение пишут так, чтобы было понятно, что исправить.
Вызывающий код может перехватить исключение в try/except и решить, что делать дальше. Запись
except ValueError as error даёт перехваченному исключению имя: str(error) — его сообщение.
try:
cup.volume = 10
print("записали") # не выполнится
except ValueError as error:
print("не получилось:", error)
print(cup.volume)не получилось: объём 10 мл — меньше 50 мл не бывает 300
Допишите класс Product: свойство price возвращает _price, а сеттер записывает новую цену, если она больше
нуля, иначе выбрасывает ValueError с сообщением "цена должна быть больше нуля". Неудачная попытка не меняет
старую цену.
@price.setter, затем def price(self, value):. Внутри — if value <= 0: raise ValueError(...) и после проверки self._price = value.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price # запись через свойство: сработает сеттер
@property
def price(self):
...
@price.setter
def price(self, value):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value <= 0:
raise ValueError("цена должна быть больше нуля")
self._price = valueПроверка при создании
Сеттер срабатывает при любой записи в свойство — и в __init__ тоже. Если в __init__ написать
self.volume = volume (без подчёркивания), неверный экземпляр просто не создастся:
class Cup:
def __init__(self, volume):
self.volume = volume # через свойство — с проверкой
@property
def volume(self):
return self._volume
@volume.setter
def volume(self, value):
if value < 50:
raise ValueError(f"объём {value} мл — меньше 50 мл не бывает")
self._volume = value
tiny = Cup(20)ValueError: объём 20 мл — меньше 50 мл не бывает
В __init__ класса с сеттером цены написано self._price = price. Что будет при Product("Чай", -5)?
_price — это запись в обычный атрибут, сеттер price при этом не вызывается. Чтобы проверка работала и при создании, __init__ должен писать в свойство: self.price = price.Объявите класс Review: Review("Анна", 5) хранит имя автора в атрибуте author и оценку в свойстве stars.
Оценка — число от 1 до 5; число вне этого диапазона — и при создании, и при изменении — выбрасывает
ValueError.
__init__: self.stars = stars — запись через свойство, чтобы сработала проверка._stars.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Review:
def __init__(self, author, stars):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Review:
def __init__(self, author, stars):
self.author = author
self.stars = stars
@property
def stars(self):
return self._stars
@stars.setter
def stars(self, value):
if value < 1 or value > 5:
raise ValueError("оценка — от 1 до 5")
self._stars = valueНеверные данные на входе
Проверки в сеттерах особенно полезны, когда данные приходят извне — из файла или формы. Объект сам не даст создать себя с ошибкой, а программа перехватит исключение и пропустит плохую строку.
В rows — строки прайса «название, цена», среди них есть ошибочные. Создайте список products из
экземпляров Product (из упражнения про цену) для верных строк, а в skipped сохраните названия
пропущенных — тех, где Product(...) выбросил ValueError.
try: — создать товар и добавить в products; except ValueError: — добавить название в skipped.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
rows = [("Эспрессо", 150), ("Капучино", -220), ("Чай", 120), ("Раф", 0), ("Какао", 190)]
products = ...
skipped = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
rows = [("Эспрессо", 150), ("Капучино", -220), ("Чай", 120), ("Раф", 0), ("Какао", 190)]
products = []
skipped = []
for name, price in rows:
try:
products.append(Product(name, price))
except ValueError:
skipped.append(name)