Согласитесь, что использовать операторы куда удобнее, чем обыкновенные методы. Давайте вспомним о реализованном нами методе move(x, y)
и напишем ему альтернативу в виде операторов +
и +=
.
При выполнении кода point + (x, y)
, создаётся новая точка, которая отличается от изначальной на заданное кортежем расстояние по осям x и y.
При выполнении кода point += (x, y)
производится перемещение изначальной точки.
Напомним, что сейчас мы модернизируем только класс PatchedPoint
.
Примечание
Ваше решение должно содержать только классы и функции.
В решении не должно быть вызовов инициализации требуемых классов.
Пример
Ввод
point = PatchedPoint()
print(point)
new_point = point + (2, -3)
print(point, new_point, point is new_point)
Вывод
(0, 0)
(0, 0) (2, -3) False
Ввод
first_point = second_point = PatchedPoint((2, -7))
first_point += (7, 3)
print(first_point, second_point, first_point is second_point)
Вывод
(9, -4) (9, -4) True
Решение
Настала очередь попробовать волшебные методы __add__ и __iadd__. Первый реализует операцию +, второй – +=. И как и в прошлой задаче эти методы имеют определенную разницу не только с точки зрения нотации, но и с точки зрения того, что мы получаем в результате. Важное отличие в том, что __add__ должен ввернуть новый объект, никак не связанный ни с первым, ни вторым, в то время как __iadd__ модифицирует первый объект. И этот нюанс иногда вызывает сложность, если модификация объекта не представляет никаких проблем, мы это уже делали несколько раз в прошлых задачах, то порождение нового, не всегда очевидная задача для начинающих программистов просто в силу ловушки нашего привычного хода мыслей. В то же время это элементарное действие – для порождения нового объекта достаточно его инициализировать нужными значениями и вернуть в качестве результата.
Посмотреть код
Решение
class Point:
def __init__(self, x, y) -> None:
self.x = x
self.y = y
def move(self, new_x, new_y):
self.x += new_x
self.y += new_y
def length(self, point):
result = ((point.x - self.x) ** 2 + (point.y - self.y) ** 2) ** 0.5
return round(result, 2)
class PatchedPoint(Point):
def __init__(self, *args) -> None:
match len(args):
case 0:
self.x = 0
self.y = 0
case 1:
self.x, self.y = args[0]
case 2:
self.x, self.y = args
def __add__(self, other):
return PatchedPoint(self.x + other[0], self.y + other[1])
def __iadd__(self, other):
self.move(other[0], other[1])
return self
def __str__(self) -> str:
string = f'({self.x}, {self.y})'
return string
def __repr__(self) -> str:
string = f'PatchedPoint({self.x}, {self.y})'
return string
Решение
class Point:
def __init__(self, x, y) -> None:
self.x = x
self.y = y
def move(self, new_x, new_y):
self.x += new_x
self.y += new_y
def length(self, point):
result = ((point.x - self.x) ** 2 + (point.y - self.y) ** 2) ** 0.5
return round(result, 2)
class PatchedPoint(Point):
def __init__(self, *args) -> None:
match len(args):
case 0:
super().__init__(0, 0)
case 1:
super().__init__(*args[0])
case 2:
super().__init__(*args)
def __add__(self, other):
return PatchedPoint(self.x + other[0], self.y + other[1])
def __iadd__(self, other):
self.move(other[0], other[1])
return self
def __str__(self) -> str:
string = f'({self.x}, {self.y})'
return string
def __repr__(self) -> str:
string = f'PatchedPoint({self.x}, {self.y})'
return string
Сергей, здравствуйте.
Объясните, пожалуйста, почему так получается.
point_1 и point_2 – это переменные, которые ссылаются на один и тот же объект – PatchedPoint(2, -7). Если мы меняем point_1, то меняется сам объект PatchedPoint(2, -7). Соответственно, ссылаясь на PatchedPoint(2, -7), point_2 дает тот же результат, что и point_1. И point_1 и point_2 как были одним и тем же объектом PatchedPoint(2, -7), так и остаются. Поэтому после __iadd__ point_1 is point_2 (True).
Но все это “работает”, только если в теле __iadd__ мы обращаемся к методу move(), предусмотренном в родительском классе.
А вот если в классе PatchedPoint будет свой move(), то производимые в __iadd__ изменения затронут только тот экземпляр класса PatchedPoint, для которого они указаны (в нашем случае – point_1).
Почему так? Почему point_1 и point_2 перестают ссылаться на один и тот же объект (на прежний объект ссылается только point_2, а point_1 уже становится другим объектом)?
Вывод:
(0, 0)
(0, 0) (2, -3) False
113903448336 113903448336
(9, -4) (2, -7) False
113903448432 113903448336
Я боюсь, что это просто невнимательность. У вас в коде не реализован метод __iadd__ и поэтому срабатывает метод __add__, что буду откровенен для меня явилось сюрпризом. А этот метод порождает новый объект. Вероятнее всего в момент переноса move() вы удалили __iadd__ и получилась такая вот путаница.
Сергей, здравствуйте. Большое спасибо за ответ.
Простите меня, пожалуйста, за путаницу. Возникшую, да, вследствие моей невнимательности. Только невнимательности не при написании кода, а при формировании вопроса.
Дело в том, что я, если можно так сказать, экспериментировала. И в коде я совершенно сознательно не реализовывала метод __iadd__ (вопреки условиям задачи), а заменила его на “собственный” move() в производном классе.
Мой вопрос должен был звучать так: «…Но все это “работает”, только если в теле __iadd__ мы обращаемся к методу move(), предусмотренном в родительском классе.
А вот если в классе PatchedPoint будет свой move(), то производимые в этом move() изменения затронут только тот экземпляр класса PatchedPoint, для которого они указаны (в нашем случае – point_1)».
Ну и вот, почему-то при этом порождается новый объект. И я не понимала, почему он порождается.
Вы объяснили, что это происходит потому, что срабатывает метод __add__. Да, тогда порождение нового объекта абсолютно понятно. Спасибо!
А вот почему срабатывает метод __add__, – и для Вас сюрприз…
Еще раз очень-очень прошу прощения за некорректность вопроса. И еще раз спасибо.
А еще я поняла, что в отсутствии __iadd__ некорректен ввод , предусмотренный условиями задачи. Если уж я решила отказаться от __iadd__, а вместо него указать move() в производном классе, тогда и ввод надо было менять соответственно.
И тогда все будет корректно. Благодаря Вашему ответу я это осознала.
Ввод вполне корректен. И такое поведение даже задокументировано:
If __iadd__() does not exist, or if x.__iadd__(y) returns NotImplemented, x.__add__(y) and y.__radd__(x) are considered, as with the evaluation of x + y.