C. Классная точка 5.0

Согласитесь, что использовать операторы куда удобнее, чем обыкновенные методы. Давайте вспомним о реализованном нами методе 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__ модифицирует первый объект. И этот нюанс иногда вызывает сложность, если модификация объекта не представляет никаких проблем, мы это уже делали несколько раз в прошлых задачах, то порождение нового, не всегда очевидная задача для начинающих программистов просто в силу ловушки нашего привычного хода мыслей. В то же время это элементарное действие – для порождения нового объекта достаточно его инициализировать нужными значениями и вернуть в качестве результата.

Посмотреть код

Решение

Python
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

Решение

Python
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
Подписаться
Уведомить о
guest
5 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Ольга
Ольга
12.11.2024 15:10

Сергей, здравствуйте.
Объясните, пожалуйста, почему так получается.
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 уже становится другим объектом)?

class Point:

    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y

    def move(self, shift_x, shift_y):
        self.x += shift_x
        self.y += shift_y

    def length(self, new_point):
        return round((abs(self.x - new_point.x) ** 2 + abs(self.y - new_point.y) ** 2) ** 0.5, 2)


class PatchedPoint(Point):

    def __init__(self, *args):
        match len(args):
            case 0:
                super().__init__(0, 0)
            case 1:
                super().__init__(*args[0])
            case 2:
                super().__init__(*args)

    def __str__(self) -> str:
        return f'({self.x}, {self.y})'

    def __repr__(self) -> str:
        return f'PatchedPoint({self.x}, {self.y})'

    def __add__(self, other):
        return PatchedPoint(self.x + other[0], self.y + other[1])

    def move(self, shift_x, shift_y):
        self.x += shift_x
        self.y += shift_y
        return self


point = PatchedPoint()
print(point)
new_point = point + (2, -3)
print(point, new_point, point is new_point)
point_1 = point_2 = PatchedPoint((2, -7))
print(id(point_1), id(point_2))
point_1 += (7, 3)
print(point_1, point_2, point_1 is point_2)
print(id(point_1), id(point_2))

Вывод:
(0, 0)
(0, 0) (2, -3) False
113903448336 113903448336
(9, -4) (2, -7) False
113903448432 113903448336

Ольга
Ольга
Ответить на  Сергей Клочко
13.11.2024 14:49

Сергей, здравствуйте. Большое спасибо за ответ.
Простите меня, пожалуйста, за путаницу. Возникшую, да, вследствие моей невнимательности. Только невнимательности не при написании кода, а при формировании вопроса.
Дело в том, что я, если можно так сказать, экспериментировала. И в коде я совершенно сознательно не реализовывала метод __iadd__ (вопреки условиям задачи), а заменила его на “собственный” move() в производном классе.
Мой вопрос должен был звучать так: «…Но все это “работает”, только если в теле __iadd__ мы обращаемся к методу move(), предусмотренном в родительском классе.
А вот если в классе PatchedPoint будет свой move(), то производимые в этом move() изменения затронут только тот экземпляр класса PatchedPoint, для которого они указаны (в нашем случае – point_1)».
Ну и вот, почему-то при этом порождается новый объект. И я не понимала, почему он порождается.
Вы объяснили, что это происходит потому, что срабатывает метод __add__. Да, тогда порождение нового объекта абсолютно понятно. Спасибо!
А вот почему срабатывает метод __add__, – и для Вас сюрприз…
Еще раз очень-очень прошу прощения за некорректность вопроса. И еще раз спасибо.

Ольга
Ольга
13.11.2024 15:16

А еще я поняла, что в отсутствии __iadd__ некорректен ввод , предусмотренный условиями задачи. Если уж я решила отказаться от __iadd__, а вместо него указать move() в производном классе, тогда и ввод надо было менять соответственно.
И тогда все будет корректно. Благодаря Вашему ответу я это осознала.