Перегрузка арифметических операторов и операторов сравнения
Встроенные типы данных поддерживают множество стандартных операций. Например, числа поддерживают все арифметические операции, а строки мы можем складывать и умножать на число.
Однако, когда вы создаёте собственный класс, Python использует поведение по умолчанию и не знает, какое логическое значение вы вкладываете в операции сложения или сравнения для объектов этого класса.
Например, давайте вспомним, что в математике есть такое понятие как вектор – направленный отрезок, соединяющий две точки. Тогда координатами вектора AB с началом в точке A с координатами (x1; y1) и концом в точке B с координатами (x2; y2) является разность соответствующих координат конца и начала, то есть (x2 - x1; y2 - y1). Поэтому при создании класса вектора Vector мы можем инициализировать его координатами x и y, представляющими собой его смещение относительно начала координат:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
Теперь давайте создадим два вектора и попробуем их сложить:
vector1 = Vector(3, 5)
vector2 = Vector(1, 6)
print(vector1 + vector2)
# Ошибка: TypeError: unsupported operand type(s) for +: 'Vector' and 'Vector'
Ожидаемо, но мы столкнулись с ошибкой TypeError, ведь Python не умеет складывать объекты класса Vector. Однако Python предоставляет специальный механизм, называемый перегрузкой операторов, который позволяет определять специальное поведение операторов (таких как +, - или * и другие) для пользовательских классов. Это осуществляется с помощью специальных магических методов, например, для операции сложения используется метод __add__().
Перегрузка оператора сложения
Для сложения двух векторов следует сложить соответствующие координаты этих векторов. То есть суммой вектора с координатами (3; 5) и вектора с координатами (1; 6) должен быть новый вектор с координатами (4; 11).
В классе Vector мы можем реализовать это с помощью метода __add__(), который принимает ссылку на экземпляр класса self и объект other, с которым производиться сложение. В данном случае объект other также является экземпляром класса Vector, поэтому мы можем получить координаты этого вектора как атрибуты этого объекта:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __add__(self, other: "Vector") -> "Vector":
new_x = self.x + other.x
new_y = self.y + other.y
return Vector(new_x, new_y)
При этом арифметические операции не должны изменять исходные объекты, поэтому внутри метода __add__() мы определяем, как сложить компоненты, и возвращаем новый объект класса Vector.
Теперь, когда Python встретит оператор сложения (+) между двумя объектами класса Vector, он будет искать метод __add__() в левом операнде:
vector1 = Vector(3, 5)
vector2 = Vector(1, 6)
print(vector1 + vector2)
# Вывод: Vector(x=4, y=11)
Благодаря перегрузке, мы можем использовать знакомый и интуитивно понятный оператор + для выполнения специфической для нашего класса операции сложения векторов.
Перегрузка арифметических операторов
Перегрузка арифметических операторов позволяет объектам собственных классов участвовать во всех стандартных арифметических операциях, доступных для встроенных типов данных. То есть мы можем определить не только сложение, но и умножение, нахождение остатка от деления и даже возведение в степень.
При этом магические методы, используемые для перегрузки бинарных операторов, принимают два аргумента:
self– ссылка на объект, на котором вызывается метод (левый операнд);other– ссылка на объект, используемый в операции (правый операнд).
Когда вы пишете выражение с оператором, Python автоматически переводит его в вызов метода, где порядок аргументов строго определен. Поэтому рассмотренный ранее метод __add__() в выражении vector1 + vector2 преобразуется в vector1.__add__(vector2), то есть он вызывается именно на левом операнде vector1.
|
Метод |
Оператор |
Операция |
Сокращение от |
|---|---|---|---|
|
|
|
Сложение |
Addition |
|
|
|
Вычитание |
Subtraction |
|
|
|
Умножение |
Multiplication |
|
|
|
Деление |
True division |
|
|
|
Целочисленное деление |
Floor division |
|
|
|
Остаток от деления |
Modulo |
|
|
|
Возведение в степень |
Power |
Для примера давайте определим операцию умножения вектора на число. Для этого нужно умножить каждую координату вектора на это число:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __mul__(self, scalar: int | float) -> "Vector":
new_x = self.x * scalar
new_y = self.y * scalar
return Vector(new_x, new_y)
Обратите внимание, что необязательно называть второй аргумент именем other. Например, здесь более наглядным будет назвать число, на которое умножается вектор, именем scalar.
Теперь умножим вектор с координатами (2; 7) на число 2:
vector = Vector(2, 7)
print(vector * 2)
# Вывод: Vector(x=4, y=14)
В математике, от перемены мест множителей произведение не меняется, поэтому давайте умножим число 2 на экземпляр класса Vector:
vector = Vector(2, 7)
print(2 * vector)
# Ошибка: TypeError: unsupported operand type(s) for *: 'int' and 'Vector'
И здесь мы столкнёмся с исключением TypeError, ведь Python вызывает метод __mul__() на левом операнде – целом числе встроенного типа int, который не работает с объектами класса Vector.
Однако на самом деле в таком случае Python не сразу вызывает исключение, а пробует вызвать отражённый метод на правом операнде. Такие методы отличаются от тех, которые мы уже рассмотрели, только префиксом «r» (от англ. reflected) в начале.
|
Метод |
Оператор |
Операция |
|---|---|---|
|
|
|
Сложение (отражённое) |
|
|
|
Вычитание (отражённое) |
|
|
|
Умножение (отражённое) |
|
|
|
Деление (отражённое) |
|
|
|
Целочисленное деление (отражённое) |
|
|
|
Остаток от деления (отражённое) |
|
|
|
Возведение в степень (отражённое) |
То есть если оператор не определён для левого операнда, то Python сначала ищет отражённый метод с префиксом «r» в правом операнде и только потом вызывает исключение.
И если в классе Vector определить метод __rmul__(), то мы можем умножать как целые, так и вещественные числа объекты этого класса:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __mul__(self, scalar: int | float) -> "Vector":
new_x = self.x * scalar
new_y = self.y * scalar
return Vector(new_x, new_y)
def __rmul__(self, scalar: int | float) -> "Vector":
return self.__mul__(scalar)
vector = Vector(2, 7)
print(2 * vector)
# Вывод: Vector(x=4, y=14)
print(1.5 * vector)
# Вывод: Vector(x=3.0, y=10.5)
Здесь отражённый метод __rmul__() просто вызывает метод __mul__(), в котором остаётся вся логика умножения вектора на число.
При этом методы с префиксом «r» не заменяют обычные методы, а вызываются только в том случае, если операция не поддерживается левым операндом. Поэтому для поддержки умножения вектора на число всё равно придётся писать обычный метод __mul__().
Также мы уже знаем, что арифметические операторы можно совмещать с оператором присваивания, поэтому Python позволяет определить такие операторы для объектов собственных классов. Для этого достаточно к обычным методам добавить префикс «i» (от англ. in-place).
|
Метод |
Оператор |
Операция |
|---|---|---|
|
|
|
Сложение с присваиванием |
|
|
|
Вычитание с присваиванием |
|
|
|
Умножение с присваиванием |
|
|
|
Деление с присваиванием |
|
|
|
Целочисленное деление с присваиванием |
|
|
|
Остаток от деления с присваиванием |
|
|
|
Возведение в степень с присваиванием |
Рассмотренные ранее методы, например, __add__(), создают и возвращают новый объект, оставляя исходный объект неизменным. Однако методы с префиксом «i» изменяют состояние объекта self и возвращают его же.
Например, определим операцию вычитания с присваиванием:
class Vector:
def __init__(self, x: int | float, y: int | float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector(x={self.x}, y={self.y})"
def __isub__(self, other: "Vector") -> "Vector":
self.x -= other.x
self.y -= other.y
return self
vector1 = Vector(10, 6)
vector2 = Vector(5, 2)
vector1 -= vector2
print(vector1)
# Вывод: Vector(x=5, y=4)
Когда Python видит операцию vector1 -= vector2, он вызывает метод vector1.__isub__(vector2), который изменяет исходный объект vector1. Однако, если метод __isub()__ не был определён, Python попытается выполнить операцию через __sub()__, а затем присвоить результат обратно переменной.
Перегрузка операций сравнения
Перегрузка операторов сравнения дает возможность определить, что означает, что один объект равен другому или нет, а также больше или меньше его.
Например, если вы создаете свой собственный класс и не реализуете метод перегрузки оператора проверки на равенство (==), то этот оператор по умолчанию будет вести себя так же, как оператор is, то есть проверять, ссылаются ли две переменные на один и тот же объект в памяти
Вместо того чтобы полагаться на сравнение адресов объектов в памяти, следует настроить сравнение по тем логически значимым атрибутам, которые действительно определяют сущность и взаимосвязь объектов.
|
Метод |
Оператор |
Операция |
Сокращение от |
|---|---|---|---|
|
|
|
Равенство |
Equal |
|
|
|
Неравенство |
Not equal |
|
|
|
Строго больше |
Greater than |
|
|
|
Строго меньше |
Less than |
|
|
|
Больше или равно |
Greater or equal |
|
|
|
Меньше или равно |
Less or equal |
Представим класс для денег Money, который хранит сумму amount и валюту currency. Два объекта Money логически равны, если их сумма и валюта совпадают:
class Money:
def __init__(self, amount, currency):
self.amount = amount
self.currency = currency
def __eq__(self, other: "Money") -> bool:
# Проверка равенства количества
same_amount = self.amount == other.amount
# Проверка равенства валюты
same_currency = self.currency == other.currency
return same_amount and same_currency
Метод проверки на равенство __eq__() является одним из самых важных. По умолчанию он наследуется от object и просто проверяет, являются ли два объекта одним и тем же объектом в памяти. Переопределяя его, вы определяете логическое равенство по собственным требованиям:
money1 = Money(100, "RUB")
money2 = Money(100, "RUB")
money3 = Money(150, "USD")
money4 = Money(100, "EUR")
print(money1 == money2)
# Вывод: True (одинаковая сумма и валюта)
print(money1 == money3)
# Вывод: False (разная сумма)
print(money1 == money4)
# Вывод: False (разная валюта)
Если вы определяете __eq__, то вам необязательно определять метод проверки на неравенство __ne__(), так как Python использует следующую логику: money1 != money2 эквивалентно not (money1 == money2):
print(money1 != money2)
# Вывод: False (одинаковая сумма и валюта)
print(money1 != money3)
# Вывод: True (разная сумма)
print(money1 != money4)
# Вывод: True (разная валюта)
Также в классе Money мы можем определить, что один объект больше другого, если у него больше сумма, при условии, что валюта одинакова:
class Money:
def __init__(self, amount: int | float, currency: str):
self.amount = amount
self.currency = currency
def __lt__(self, other: "Money") -> bool:
if self.currency != other.currency:
raise ValueError("Нельзя сравнивать деньги в разных валютах")
return self.amount < other.amount # Сравнение по сумме
money1 = Money(100, "RUB")
money2 = Money(200, "RUB")
print(money1 < money2)
# Вывод: True (100 < 200)
Аналогичным образом определяются и другие операторы сравнения.
Взаимодействие с объектами разных типов данных
Бинарные операторы выполняют операцию над двумя операндами. И если левый операнд self гарантированно является ссылкой на экземпляр разрабатываемого класса, то в качестве правого операнда other может быть передан объект любого типа.
Однако функция isinstance() позволяет проверить тип правого операнда other перед совершением операции внутри магического метода. Если она возвращает True, то операция совершается, но если объект other имеет неподходящий тип, и функция возвращает False, то возможно два варианта:
- Возврат константы
NotImplemented, если есть вероятность, что правый операнд может совершить операцию с помощью отражённого метода. То есть операция является коммутативной и её результат не зависит от порядка операндов. - Вызов исключения с помощью ключевого слова
raise, если операция не должна быть разрешена, даже если объектotherимеет отраженный метод.
Представим класс Score, который хранит количество очков value. Объекты этого класса должны складываться друг с другом, а также с обычными числами:
class Score:
def __init__(self, value: int | float):
self.value = value
def __repr__(self) -> str:
return f"Score({self.value})"
def __add__(self, other: "Score") -> "Score":
# Если other - это объект класса Score
if isinstance(other, Score):
new_value = self.value + other.value
return Score(new_value)
# Если other - это число
elif isinstance(other, (int, float)):
new_value = self.value + other
return Score(new_value)
return NotImplemented
def __radd__(self, other: "Score") -> "Score":
return self.__add__(other)
Проверка типа объекта other позволяет реализовать как сложение двух объектов класса Score:
score1 = Score(10)
score2 = Score(5)
print(score1 + score2)
# Вывод: Score(15)
Так и сложение объекта этого класса с числом:
print(score2 + 25)
# Вывод: Score(30)
При этом отражённый метод __radd__() возвращает метод __add__(), так как сложение, как и умножение, коммутативно, то есть порядок слагаемых не важен:
print(10 + score1)
# Вывод: Score(20)
Если объект other не является ни объектом класса Score, ни числом, то возвращается константа NotImplemented, указывающая Python не сразу вызывать исключение, а вызвать отражённый метод на правом операнде. Поэтому сложение объекта класса Score со строкой приведёт к исключению TypeError:
print(score1 + "12")
# Вывод: TypeError: unsupported operand type(s) for +: 'Score' and 'str'
Однако это исключение возникает после того, как была сделана попытка вызвать отражённый метод __radd__() на объекте класса str.
Если такое поведение недопустимо и исключение следует вызывать сразу в магическом методе, то используется ключевое слово raise. После него указывается тип вызываемого исключения, а в скобках может быть указан текст, который будет выводиться на экран.
Например, запретим операцию сложения для любого объекта, не являющегося экземпляром класса Score:
class Score:
def __init__(self, value: int | float):
self.value = value
def __repr__(self) -> str:
return f"Score({self.value})"
def __add__(self, other: "Score") -> "Score":
if isinstance(other, Score):
new_value = self.value + other.value
return Score(new_value)
raise TypeError(f"Нельзя сложить Score с типом {type(other)}")
def __radd__(self, other: "Score") -> "Score":
return self.__add__(other)
Теперь исключение вызывается сразу же на объекте класса Score, а также выводится именно то сообщение об исключении, которое мы написали:
score1 = Score(10)
print(score1 + 2)
# Вывод: TypeError: Нельзя сложить Score с типом <class 'int'>
Примеры
Пример 1. Управление временем
Класс Duration описывает отрезок времени в минутах (например, длительность фильма или перерыва) с помощью атрибута minutes, и обеспечивает вычитание одного времени из другого, выполнение целочисленного деления (сколько полных раз умещается одно время в другом) и проверку на равенство:
class Duration:
"""Описывает отрезок времени в минутах."""
def __init__(self, minutes: int):
"""Конструктор класса Duration.
Параметры:
minutes: Количество минут в отрезке времени.
"""
self.minutes = minutes
def __str__(self) -> str:
"""Пользовательское описание объекта."""
return f"{self.minutes} мин."
def __sub__(self, other: "Duration") -> "Duration":
"""Перегрузка оператора вычитания (-)."""
if not isinstance(other, Duration):
return NotImplemented
new_minutes = self.minutes - other.minutes
return Duration(new_minutes)
def __floordiv__(self, other: "Duration") -> int:
"""Перегрузка оператора целочисленного деления (//)."""
if not isinstance(other, Duration) or other.minutes == 0:
return NotImplemented
return self.minutes // other.minutes
def __eq__(self, other: object) -> bool:
"""Перегрузка оператора равенства (==)."""
if isinstance(other, Duration):
return self.minutes == other.minutes
return NotImplemented
film = Duration(120) # Длительность фильма
trailer = Duration(5) # Длительность трейлера
break_time = Duration(15) # Длительность перерыва
# Вычитание (-)
remaining = film - break_time
print(f"{film} - {trailer} - {break_time} = {film - trailer - break_time}")
# Целочисленное деление (//)
print(f"{film} // {trailer} = {film // trailer} мин.")
# Равенство (==)
duration1 = Duration(60)
duration2 = Duration(60)
print(f"{film} == {trailer}? {film == trailer}")
print(f"{film} != {break_time}? {film != break_time}")
Вывод:
120 мин. - 5 мин. - 15 мин. = 100 мин.
120 мин. // 5 мин. = 24
120 мин. == 5 мин.? False
120 мин. != 15 мин.? True
Пример 2. Управление временем доставки
Класс DeliveryTime представляет время доставки в часах с помощью атрибута hours, и поддерживает добавление задержки и проверку на соблюдение временного лимита с по мощью операций сложения с присваиванием (+=) и сравнения на меньше или равно (<=). При этом второй операнд может быть как экземпляром этого класса, так и целым или вещественным числом:
class DeliveryTime:
"""Описывает время доставки в часах."""
def __init__(self, hours: float):
"""Конструктор класса DeliveryTime.
Параметры:
hours: Время в часах.
"""
self.hours = hours
def __str__(self):
"""Пользовательское описание объекта"""
return f"{self.hours} ч."
def __iadd__(self, other: "DeliveryTime") -> "DeliveryTime":
"""Перегрузка оператора сложения с присваиванием (+=)."""
if isinstance(other, DeliveryTime):
self.hours += other.hours
elif isinstance(other, (int, float)):
self.hours += other # Разрешаем добавление числа (часов)
return NotImplemented
def __le__(self, other: "DeliveryTime") -> bool:
"""Перегрузка оператора меньше или равно (<=)."""
if isinstance(other, DeliveryTime):
return self.hours <= other.hours
if isinstance(other, (int, float)):
return self.hours <= other # Сравнение с числом (лимитом в часах)
return NotImplemented
standard_time = DeliveryTime(24.0) # Стандартное время доставки
delay = DeliveryTime(3.5) # Задержка
limit = 48 # Лимит как целое число
# Сложение с присваиванием (+=)
delivery = standard_time
delivery += delay
print(f"Время доставки с учетом задержки: {delivery}")
delivery += 1.0 # Добавляем еще час (целое число)
print(f"Время доставки после еще часа: {delivery}")
# Сравнение (<=)
print(f"Время доставки <= {limit} часов? {delivery <= limit}")
print(f"Время доставки <= 25 часов? {delivery <= 25}")
Вывод:
Время доставки с учетом задержки: 27.5 ч.
Время доставки после еще часа: 28.5 ч.
Время доставки <= 48 часов? True
Время доставки <= 25 часов? False
Пример 3. Стоимость актива на бирже
Класс Price описывает стоимость акции или любого другого актива на бирже, которая имеет целую (рубли) и дробную (копейки) части. Так как использование стандартных вещественных чисел float может привести к ошибкам округления, то рубли и копейки хранятся отдельно в соответствующих атрибутах rubles и kopecks. Однако для расчётов и сравнения вся стоимость переводится в копейки _total_kopecks, а рубли и копейки вычисляются в методе __init__():
class Price:
"""Описывает стоимость актива."""
def __init__(self, rubles: int = 0, kopecks: int = 0):
"""Конструктор класса Price.
Нормализует копейки, чтобы их было не больше 99.
Параметры:
rubles: Часть стоимости актива в рублях.
kopecks: Часть стоимости актива в копейках.
"""
# Вся стоимость в копейках
self._total_kopecks = rubles * 100 + kopecks
self.rubles = self._total_kopecks // 100 # Целая часть - это рубли
self.kopecks = self._total_kopecks % 100 # Остаток - это копейки
def __str__(self):
"""Пользовательское описание объекта."""
return f"{self.rubles} руб. {self.kopecks} коп."
def __add__(self, other: "Price") -> "Price":
"""Перегрузка оператора сложения (+)."""
if not isinstance(other, Price):
return NotImplemented
total_kopecks = self._total_kopecks + other._total_kopecks
# Возвращаем новый объект, используя общее количество копеек
return Price(kopecks=total_kopecks)
def __sub__(self, other: "Price") -> "Price":
"""Перегрузка оператора вычитания (-)."""
if not isinstance(other, Price):
return NotImplemented
total_kopecks = self._total_kopecks - other._total_kopecks
# Стоимость не может быть отрицательной
if total_kopecks < 0:
total_kopecks = 0
return Price(kopecks=total_kopecks)
def __gt__(self, other: "Price") -> bool:
"""Перегрузка строго больше (>).
Сравнивает по общему количеству копеек.
"""
if not isinstance(other, Price):
return NotImplemented
return self._total_kopecks > other._total_kopecks
def __eq__(self, other: object) -> bool:
"""Перегрузка оператора равенства (==).
Сравнивает количество рублей и копеек.
"""
if not isinstance(other, Price):
return NotImplemented
return self.rubles == other.rubles and self.kopecks == other.kopecks
price1 = Price(rubles=52, kopecks=40)
price2 = Price(rubles=178, kopecks=87)
price3 = Price(rubles=1024, kopecks=20)
# Сложение (+)
print(f"{price1} + {price2} = {price1 + price2}")
print(f"{price2} + {price3} = {price2 + price3}", end="\n\n")
# Проверка: 5.50 + 11.50 = 17.00
# Вычитание (-)
print(f"{price2} - {price1} = {price2 - price1}")
print(f"{price3} - {price1} = {price3 - price1}", end="\n\n")
# Проверка на равенство (==)
price4 = price2
print(f"{price1} == {price2}? {price1 == price2}")
print(f"{price4} == {price2}? {price4 == price2}", end="\n\n")
# Сравнение (>)
print(f"{price1} > {price2}? {price1 > price2}")
print(f"{price2} > {price3}? {price2 > price3}")
Вывод:
52 руб. 40 коп. + 178 руб. 87 коп. = 231 руб. 27 коп.
178 руб. 87 коп. + 1024 руб. 20 коп. = 1203 руб. 7 коп.
178 руб. 87 коп. - 52 руб. 40 коп. = 126 руб. 47 коп.
1024 руб. 20 коп. - 52 руб. 40 коп. = 971 руб. 80 коп.
52 руб. 40 коп. == 178 руб. 87 коп.? False
178 руб. 87 коп. == 178 руб. 87 коп.? True
52 руб. 40 коп. > 178 руб. 87 коп.? False
178 руб. 87 коп. > 1024 руб. 20 коп.? False
Итоги
- Перегрузка операторов – это механизм, который позволяет определить поведение стандартных операторов (например,
+,-или*) для пользовательских классов. - Магические методы перегрузки операторов переопределяют арифметические операторы, включая операторы с присваиванием, а также операторы сравнения.
- Если оператор не определён для левого операнда, то Python сначала ищет отражённый метод с префиксом «r» в правом операнде и только потом вызывает исключение.
- Если тип левого операнда проверяется перед совершением операции, то если он не поддерживает эту операцию, то можно явно вернуть константу
NotImplementedдля вызова отражённого метода или вызвать исключение с помощью ключевого словаraise.
Задания для самопроверки
1. Когда Python вызывает отражённые методы перегрузки?
Если оператор не определён для левого операнда, Python пытается вызвать отражённый метод в правом операнде.
2. Создайте класс для времени Time, который инициализируется одним атрибутом total_seconds для общего количества секунд. Перегрузите оператор * (умножение) так, чтобы он умножал total_seconds на число, переданное в качестве правого операнда, и возвращал новый объект Time. Для удобного вывода информации об объекте определите метод __str__(). Выведите на экран результат операции Time(10) * 3.
class Time:
def __init__(self, total_seconds: int):
self.total_seconds = total_seconds
def __str__(self) -> str:
return f"Time({self.total_seconds})"
def __mul__(self, other: int) -> "Time":
if not isinstance(other, (int, float)):
return NotImplemented
new_value = self.total_seconds * other
return Time(int(new_value))
print(Time(10) * 3)
# Вывод: Time(30)
3. Для класса Time из предыдущего задания реализуйте метод отражённого умножения __rmul__() для умножения числа на объект класса Time. Выведите на экран результат операции 4 * Time(15).
class Time:
def __init__(self, total_seconds: int):
self.total_seconds = total_seconds
def __str__(self) -> str:
return f"Time({self.total_seconds})"
def __mul__(self, other: int) -> "Time":
if not isinstance(other, (int, float)):
return NotImplemented
new_value = self.total_seconds * other
return Time(int(new_value))
def __rmul__(self, other: int) -> "Time":
# Логика совпадает с прямым умножением
return self.__mul__(other)
print(4 * Time(15))
# Вывод: Time(60)
4. Создайте класс для товаров Item, который инициализируется атрибутами name для имени и price для цены. Перегрузите оператор == (равенство). Два объекта Item считаются равными, если их атрибуты name и price совпадают. Выведите на экран результат сравнения Item("Атлас", 320) с Item("Атлас", 320).
class Item:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
def __eq__(self, other: object) -> bool:
if not isinstance(other, Item):
return NotImplemented
return self.name == other.name and self.price == other.price
print(Item("Атлас", 320) == Item("Атлас", 320))
# Вывод: True
5. Для класса Item из предыдущего задания перегрузите оператор < (строго меньше). Один товар считается меньше другого, если его цена price строго меньше. Выведите на экран результат сравнения Item("Блокнот", 500) с Item("Текстовыделитель", 60).
class Item:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
def __lt__(self, other: object) -> bool:
if not isinstance(other, Item):
return NotImplemented
return self.price < other.price
print(Item("Блокнот", 500) < Item("Текстовыделитель", 60))
# Вывод: False
0 комментариев