Товары в кассу часто приходят не по одному вызову Product("Латте", 220), а строками из файла
("Латте;220") или словарями из формы. Разбирать строку перед каждым созданием экземпляра — неудобно, и
делать это в __init__ тоже не хочется: тогда обычный вызов Product("Латте", 220) перестанет работать.
Нужен второй способ создать экземпляр — альтернативный конструктор.
@classmethod
Метод с декоратором @classmethod — метод класса (class method). Первым параметром он получает не
экземпляр, а сам класс — по соглашению этот параметр называют cls. Вызывают его через класс:
Product.from_line("Латте;220"). Внутри cls(...) — то же, что вызов класса, то есть создание экземпляра:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.price})"
@classmethod
def from_line(cls, line):
name, price = line.split(";")
return cls(name, int(price))
print(Product.from_line("Латте;220"))
print([Product.from_line(line) for line in ["Чай;120", "Раф;260"]])Product('Латте', 220)
[Product('Чай', 120), Product('Раф', 260)]Допишите класс Drink: метод класса from_line(line) создаёт напиток из строки вида "Латте;220;300" —
название, цена и объём через точку с запятой. Цена и объём — целые числа.
name, price, volume = line.split(";"); числа — int(price) и int(volume).cls(name, ..., ...), а не Drink(...) — почему, расскажет следующий раздел.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Drink:
def __init__(self, name, price, volume):
self.name = name
self.price = price
self.volume = volume
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.price}, {self.volume})"
@classmethod
def from_line(cls, line):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Drink:
def __init__(self, name, price, volume):
self.name = name
self.price = price
self.volume = volume
def __repr__(self):
return f"{type(self).__name__}({self.name!r}, {self.price}, {self.volume})"
@classmethod
def from_line(cls, line):
name, price, volume = line.split(";")
return cls(name, int(price), int(volume))Почему cls, а не имя класса
Метод класса наследуется. Если подкласс вызывает from_line, то cls — это подкласс, и создаётся
экземпляр подкласса. А Drink(...), записанный внутри метода явно, всегда создаёт Drink — даже когда
вызвали SeasonalDrink.from_line(...):
class SeasonalProduct(Product):
pass
print(SeasonalProduct.from_line("Глинтвейн;300"))
print(type(SeasonalProduct.from_line("Глинтвейн;300")).__name__)SeasonalProduct('Глинтвейн', 300)
SeasonalProductДопишите класс Customer: метод класса from_dict(data) создаёт покупателя из словаря
{"name": "Анна", "phone": "+7 900 111-22-33"}. Если в словаре нет ключа "points", баллов 0, иначе —
значение из словаря.
data.get("points", 0) возвращает 0, если ключа нет.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Customer:
def __init__(self, name, phone, points=0):
self.name = name
self.phone = phone
self.points = points
@classmethod
def from_dict(cls, data):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Customer:
def __init__(self, name, phone, points=0):
self.name = name
self.phone = phone
self.points = points
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["phone"], data.get("points", 0))@staticmethod
Иногда функция логически относится к классу, но ей не нужны ни экземпляр, ни класс — например, проверка
формата телефона. Такую функцию объявляют внутри класса с декоратором @staticmethod — это статический
метод (static method): без self и без cls. Вызывают его через класс или экземпляр:
class PriceTag:
def __init__(self, name, price):
self.name = name
self.price = price
@staticmethod
def parse_price(text): # ни self, ни cls
return int(text.replace("₽", "").strip())
@classmethod
def from_text(cls, name, text):
return cls(name, cls.parse_price(text))
print(PriceTag.parse_price("220 ₽"))
print(PriceTag.from_text("Латте", "240 ₽").price)220 240
Допишите в класс Customer статический метод normalize_phone(phone): он оставляет в строке только цифры
и возвращает их строкой, если цифр ровно 11, а иначе выбрасывает ValueError. Например,
Customer.normalize_phone("+7 (900) 111-22-33") — "79001112233". Скопируйте класс из упражнения выше.
"".join(ch for ch in phone if ch.isdigit()).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
class Customer:
# скопируйте сюда __init__ и from_dict из упражнения выше
@staticmethod
def normalize_phone(phone):
...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
class Customer:
def __init__(self, name, phone, points=0):
self.name = name
self.phone = phone
self.points = points
@classmethod
def from_dict(cls, data):
return cls(data["name"], data["phone"], data.get("points", 0))
@staticmethod
def normalize_phone(phone):
digits = "".join(ch for ch in phone if ch.isdigit())
if len(digits) != 11:
raise ValueError("в номере телефона должно быть 11 цифр")
return digitsКакой метод выбрать
| Метод | Первый параметр | Когда |
|---|---|---|
| обычный | self — экземпляр |
работает с данными экземпляра |
@classmethod |
cls — класс |
создаёт экземпляры (альтернативный конструктор) или работает с атрибутами класса |
@staticmethod |
нет | вспомогательная функция, которая по смыслу относится к классу |
Какой метод нужен для Order.from_receipt(text), который разбирает текст чека и возвращает новый заказ?
self неоткуда взять. Метод класса получает класс и создаёт экземпляр вызовом cls(...) — и для подклассов тоже. Статический метод мог бы вернуть Order(...), но потерял бы подклассы.