Введение
В Python есть чрезвычайно полезный, но часто упускаемый из виду инструмент — dunder-методы. Это специальные методы, которые используются всеми классами и позволяют нам настраивать поведение определяемых пользователем классов. Если вы раньше программировали на Python, вы, вероятно, уже знакомы с наиболее распространенными методами dunder: __init__ и __str__.
Тем не менее, существуют десятки этих дандер-методов. Они позволяют нам настраивать поведение создаваемых нами классов в различных контекстах. В этом посте мы рассмотрим, как изменить поведение класса при использовании в арифметических операциях, таких как +, -, * и /. .
Я должен признаться — я не люблю числа. Итак, мы собираемся использовать методы дандера Python для создания нашей собственной системы счисления, называемой кватернионами.
Изучайте Python и приобретайте навыки, чтобы получить свою первую работу, с помощью нашего Учебного курса для профессиональных разработчиков Python.
Что такое кватернионы?
Кватернионы — это расширение комплексных чисел, имеющее следующую структуру:
Они находят применение во многих областях математики, физики и техники, робототехники и графики видеоигр, особенно потому, что они позволяют нам легко представлять повороты и ориентации в трехмерных пространствах. Чтобы узнать больше, вы можете посмотреть это видео с подробным объяснением, созданным отличным каналом YouTube 3blue1brown.
Кватернионы в Python
Для реализации кватернионов в Python мы создадим новый класс и переопределим, как эти числа складывают, вычитают, умножают и разделить. Начнем с создания метода __init__, где мы определим коэффициенты действительной и мнимой частей нашего кватерниона.
class Quaternion: def __init__(self, a, b, c, d): self.a = a self.b = b self.c = c self.d = d
Затем мы перезаписываем метод __str__, чтобы создать адекватное текстовое представление для этого типа числа:
class Quaternion: ... def __str__(self): return f"{self.a} + {self.b}i + {self.c}j + {self.d}k"
Теперь мы уже можем создать и увидеть наш первый кватернион:
q1 = Quaternion(1, 2, 3, 4) print(q1) # Result: 1 + 2i + 3j + 4k
Следующим шагом является адаптация этого класса для выполнения с ним основных арифметических операций. Начнем с определения того, как добавлять два кватерниона. Для этого мы можем перезаписать функцию __add__, которая принимает в качестве параметра другой кватернион. Сумма двух кватернионов рассчитывается как:
Итак, в Python мы можем реализовать эту операцию как:
class Quaternion: ... def __add__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for +: 'Quaternion' and '{type(other).__name__}'") return Quaternion(self.a + other.a, self.b + other.b, self.c + other.c, self.d + other.d)
С другой стороны, вычитание кватернионов определяется как:
Опять же, в Python мы можем реализовать это следующим образом:
class Quaternion: ... def __sub__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for -: 'Quaternion' and '{type(other).__name__}'") return Quaternion(self.a - other.a, self.b - other.b, self.c - other.c, self.d - other.d)
Если мы сейчас попробуем выполнить две предыдущие операции, то сможем убедиться, что они работают корректно:
q1 = Quaternion(1, 2, 3, 4) q2 = Quaternion(5, 6, 7, 8) print(q1 + q2) # Result: 6 + 8i + 10j + 12k print(q1 - q2) # Result: -4 + -4i + -4j + -4k
Следующим шагом является реализация произведения двух кватернионов, также известного как произведение Гамильтона. Эта операция немного сложнее, но ее можно записать так:
Каждая из строк определяет значение одного из коэффициентов: a, b, c и d соответственно. В Python мы можем реализовать это следующим образом, перезаписав функцию __mul__.
class Quaternion: ... def __mul__(self, other): a = self.a * other.a - self.b * other.b - self.c * other.c - self.d * other.d b = self.a * other.b + self.b * other.a + self.c * other.d - self.d * other.c c = self.a * other.c - self.b * other.d + self.b * other.a + self.d * other.b d = self.a * other.d + self.b * other.c - self.c * other.b + self.d * other.a return Quaternion(a, b, c, d)
Наконец, мы собираемся определить разделение между кватернионами. Операция проста в выполнении, хотя и немного сложна для объяснения, поскольку она включает в себя понятия нормы и сопряжения, которые выходят за рамки этого руководства. Однако вы можете углубиться в эти понятия в статье Википедии о кватернионах. Подразделение определяется как:
В коде мы можем выполнить это следующим образом:
class Quaternion: ... def __truediv__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for /: 'Quaternion' and '{type(other).__name__}'") # Calculate the squared l2 norm of the other quaternion sq_norm = other.a ** 2 + other.b ** 2 + other.c ** 2 + other.d ** 2 # Calculate the conjugate of the other quaternion conj = Quaternion(other.a, -other.b, -other.c, -other.d) # Calculate the product of the current quaternion and the conjugate of the other quaternion prod = self * conj # Divide the product by the squared l2 norm of the other quaternion return Quaternion(prod.a / sq_norm, prod.b / sq_norm, prod.c / sq_norm, prod.d / sq_norm)
Если мы сейчас протестируем эти две операции, то увидим, что они работают правильно:
q1 = Quaternion(1, 2, 3, 4) q2 = Quaternion(5, 6, 7, 8) print(q1 / q2) # Result: 0.40229885057471265 + 0.04597701149425287i + -0.028735632183908046j + 0.09195402298850575k
Рассмотренные до сих пор методы позволяют нам выполнять основные арифметические операции. Однако существует версия каждого из этих операторов, которая помимо выполнения операции присваивает результат целевой переменной. Конечно, я имею в виду операторы: +=, -=, *= и /= . В Python также есть специальные методы для перезаписи этих операторов. Они имеют то же имя, что и предыдущие, с предшествующей буквой «i»: __iadd__, __isub__, __imul__ и __itruediv__. После того, как мы реализовали предыдущие методы, их легко реализовать:
class Quaternion: ... def __iadd__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for +=: 'Quaternion' and '{type(other).__name__}'") self.a += other.a self.b += other.b self.c += other.c self.d += other.d return self def __isub__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for -=: 'Quaternion' and '{type(other).__name__}'") self.a -= other.a self.b -= other.b self.c -= other.c self.d -= other.d return self def __imul__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for *=: 'Quaternion' and '{type(other).__name__}'") self.a = self.a * other.a - self.b * other.b - self.c * other.c - self.d * other.d self.b = self.a * other.b + self.b * other.a + self.c * other.d - self.d * other.c self.c = self.a * other.c - self.b * other.d + self.b * other.a + self.d * other.b self.d = self.a * other.d + self.b * other.c - self.c * other.b + self.d * other.a return self def __itruediv__(self, other): if not isinstance(other, Quaternion): raise TypeError(f"unsupported operand type(s) for /=: 'Quaternion' and '{type(other).__name__}'") sq_norm = other.a ** 2 + other.b ** 2 + other.c ** 2 + other.d ** 2 conj = Quaternion(other.a, -other.b, -other.c, -other.d) prod = self * conj self.a = prod.a / sq_norm self.b = prod.b / sq_norm self.c = prod.c / sq_norm self.d = prod.d / sq_norm return self
Подведение итогов
Короче говоря, методы Python dunder позволяют нам изменять то, как наши объекты реагируют на различные основные операции.
В следующем посте этой серии мы продолжим рассмотрение примера с кватернионами, чтобы узнать о других методах Дандера, которые позволят нам сравнить величину наших чисел. Увидимся там!
Эту и многие другие истории читайте на сайте: