Магия в Python — создаём объект с магическими методами (ч. 2)

Добрый день, уважаемые читатели. Эта статья является продолжением статьи про магию в Питоне. Магические методы — очень гибкий инструмент разработки пользовательских классов, и сегодня мы дополним наш класс вектора, который мы начали разрабатывать в прошлой части.

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Для начала разберём изменения, которые я внёс без вашего ведома.

Изменения

Я импортировал класс Iterable (позже нам это понадобится):

from collections.abc import Iterable

Также в методе __init__() я изменил строчку:

        self.__values = list(args[0])

Теперь отправимся на путь индексации нашего объекта.

Индексация

Первый метод, который мы реализуем — __getitem__().

Конструкция object[key] равнозначна object.__getitem__(key).

Идея этого метода в нашем классе — создать возможность индексирования по итерируемому объекту. К примеру, конструкция Vector(1, 2, 3)[[0, 2]] должна возвращать Vector(1, 3) . Именно для этого мы и будем использовать класс Iterable.

def __getitem__(self, key):
    if isinstance(key, slice):
        return Vector(self.__values[key])
    elif isinstance(key, Iterable):
        return Vector([self.__values[i] for i in key])
    else:
        return self.__values[key]

Таким образом, метод индексации возвращает либо элемент вектора, либо другой экземпляр класса Vector.

Конструкция object[key] = value равноцена конструкции object.__setitem__(key, value).

def __setitem__(self, key, value):
    self.__values[key] = value

Как вы могли заметить, мы просто проводим такую же операцию со списком значений вектора и не боимся, что длина вектора как-либо изменится.

Конструкция value in object означает то же самое, что и object.__contains(value).

def __contains__(self, value):
    return value in self.__values

Если вы хотите удалить какой-либо элемент коллекции, используя конструкцию del object[key], то фактически мы вызываем следующий метод: object.__delitem__(key).

def __delitem__(self, key):
del self.__values[key]
self = Vector(self.__values)

Что важно, мы recreate экземпляр класса Vector, дабы обновить поле shape.

Операции сравнения

Далее рассмотрим операции сравнения. Ниже приведена таблица соответствия операций и магических методов, отвечающих за них.

  • object < valueobject.__lt__(value)
  • object > valueobject.__gt__(value)
  • object <= valueobject.__le__(value)
  • object >= valueobject.__ge__(value)
  • object == valueobject.__eq__(value)
  • object != valueobject.__ne__(value)

В этой статье мы не будем реализовывать булеву логику NumPy, потому реализуем два метода __eq__ и __ne__, а при вызове остальных будем возбуждать исключение.

def eq(self, value):
    return all([v1 == v2 for v1, v2 in zip(self, value)])

def __ne__(self, value):
    return not self.__eq__(value)


def __lt__(self, value):
    raise TypeError('Vector class does not support comparison operations.')


def __gt__(self, value):
    raise TypeError('Vector class does not support comparison operations.')


def __le__(self, value):
    raise TypeError('Vector class does not support comparison operations.')


def __ge__(self, value):
    raise TypeError('Vector class does not support comparison operations.')

Тестирование

Протестируем новые возможности, которые мы реализовали:

>>> v = Vector(1, 2, 3)
>>> v[1], v[0:2], v[[1, 0, 2]]
(2, Vector(<1, 2>), Vector(<2, 1, 3>))
>>> del v[0]
>>> v
Vector(<2, 3>)
>>> v = (3, 2, 1)
>>> v == (3, 2, 1), v != (1, 2)
(True, True)

В качестве бонуса: reversed(object) выполняет то же самое, что и object.__reversed__(). Реализуем и такой метод в нашем классе:

def __reversed__(self):
rev_values = reversed(self.__values)
return Vector(rev_values)
>>> v = Vector(1, 2, 3)
>>> v = reversed(v)
>>> v
Vector(<3, 2, 1>)

Заключение

В этой статье мы дополнили наш класс вектора. Я специально не пытаюсь нагрузить читателей обзором всех методов. Сделано это с целью ознакомить вас с применением «магии» на практике, а не просто привести табличку соответствий. Желаю вам, чтобы магические методы были и в вашей жизни :)

Обновленный код класса вы сможете найти здесь.

На этом статья заканчивается. Если вы заинтересованы в математическом анализе, советую прочитать «Производная. Базовые определения и термины«.

А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.