Настраиваемые магические методы для чисел

PythonBeginner
Практиковаться сейчас

Введение

В этом уроке мы рассмотрим магические методы Python, связанные с арифметическими операциями. Маггические методы - это специальные методы в классах Python, которые начинаются и заканчиваются двойными подчеркиваниями (__). Они также известны как методы "dunder" (двойные подчеркивания).

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

Мы рассмотрим следующие разделы:

  1. Унарные операторы
  2. Двоичные операторы
  3. Операции на месте

Давайте начнем!

Унарные операторы

Унарные операции - это операции, которые включают в себя один операнд, например, отрицание, абсолютное значение и т.д.

Начнем с простого объекта. В number.py создайте класс под названием MyNumber, который имеет атрибут value.

class MyNumber:
    def __init__(self, value: float):
        self.value = value

__neg__

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

    #... (предыдущий код в number.py)

    def __neg__(self) -> 'MyNumber':
        """Возвращает отрицание значения экземпляра."""
        return MyNumber(-self.value)

__abs__

Магический метод __abs__ определяет, как должна вести себя операция нахождения абсолютного значения. Когда вы используете функцию abs() для экземпляра вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __abs__(self) -> 'MyNumber':
        """Возвращает абсолютное значение значения экземпляра."""
        return MyNumber(abs(self.value))

__round__

Магический метод __round__ определяет, как должна вести себя операция округления. Когда вы используете функцию round() для экземпляра вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __round__(self, ndigits: int = None) -> 'MyNumber':
        """Округляет значение экземпляра до ближайшего целого числа или до указанного количества цифр после запятой."""
        return MyNumber(round(self.value, ndigits))

__floor__

Магический метод __floor__ определяет, как должна вести себя операция взятия целой части числа. Когда вы используете функцию math.floor() для экземпляра вашего класса, вызывается этот метод.

## модуль math должен быть импортирован в начале number.py
import math

    #... (предыдущий код в number.py)

    def __floor__(self) -> 'MyNumber':
        """Возвращает наибольшее целое число, меньшее или равное значению экземпляра."""
        return MyNumber(math.floor(self.value))

__ceil__

Магический метод __ceil__ определяет, как должна вести себя операция взятия потолка числа. Когда вы используете функцию math.ceil() для экземпляра вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __ceil__(self) -> 'MyNumber':
        """Возвращает наименьшее целое число, большее или равное значению экземпляра."""
        return MyNumber(math.ceil(self.value))

Пример: Использование унарных операторов

Теперь, когда мы определили унарные операторы для нашего класса MyNumber, посмотрим, как они работают в unary_example.py:

import math
from number import MyNumber

## Создаем новый объект MyNumber
a = MyNumber(5)
## Используем метод __neg__ с функцией print
print(f'{a.value=}, {-a.value=}')  ## Вывод: a.value=5, -a.value=-5

## Создаем еще один новый объект MyNumber
a = MyNumber(-5)
## Используем метод __abs__ с функцией print
print(f'{a.value=}, {abs(a).value=}')  ## Вывод: a.value=-5, abs(a).value=5

## Создаем третий новый объект MyNumber
a = MyNumber(5.678)
## Используем метод __round__ с функцией print
print(f'{a.value=}, {round(a, 2).value=}')  ## Вывод: a.value=5.678, round(a, 2).value=5.68

## Используем метод __floor__ с функцией print
print(f'{a.value=}, {math.floor(a).value=}')  ## Вывод: a.value=5.678, math.floor(a).value=5

## Используем метод __ceil__ с функцией print
print(f'{a.value=}, {math.ceil(a).value=}')  ## Вывод: a.value=5.678, math.ceil(a).value=6

Затем введите следующую команду в терминале, чтобы выполнить скрипт.

python unary_example.py

Двоичные операторы

Двоичные операции - это операции, которые включают в себя два операнда, такие как арифметические операции сложения, вычитания, умножения и деления, а также операции сравнения, такие как равенство, неравенство, меньше, больше и т.д.

__add__

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

    #... (предыдущий код в number.py)

    def __add__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает сумму значения экземпляра и значения другого экземпляра."""
        return MyNumber(self.value + other.value)

__sub__

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

    #... (предыдущий код в number.py)

    def __sub__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает разность между значением экземпляра и значением другого экземпляра."""
        return MyNumber(self.value - other.value)

__mul__

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

    #... (предыдущий код в number.py)

    def __mul__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает произведение значения экземпляра и значения другого экземпляра."""
        return MyNumber(self.value * other.value)

__truediv__

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

    #... (предыдущий код в number.py)

    def __truediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает результат деления значения экземпляра на значение другого экземпляра."""
        return MyNumber(self.value / other.value)

__floordiv__

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

    #... (предыдущий код в number.py)

    def __floordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает наибольшее целое число, меньшее или равное результату деления значения экземпляра на значение другого экземпляра."""
        return MyNumber(self.value // other.value)

__mod__

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

    #... (предыдущий код в number.py)

    def __mod__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает остаток от деления значения экземпляра на значение другого экземпляра."""
        return MyNumber(self.value % other.value)

__pow__

Магический метод __pow__ определяет, как должна вести себя операция возведения в степень. Когда вы используете оператор ** или функцию pow() для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __pow__(self, other: 'MyNumber') -> 'MyNumber':
        """Возвращает значение экземпляра, возведенное в степень значения другого экземпляра."""
        return MyNumber(self.value ** other.value)

Пример: Использование двоичных операторов

Теперь, когда мы определили двоичные операторы для нашего класса MyNumber, посмотрим, как они работают в binary_example.py:

from number import MyNumber

## Создаем два новых объекта MyNumber
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Вывод: a.value=5, b.value=3

## Используем метод __add__ с функцией print
print(f'{(a+b).value=}')  ## Вывод: (a+b).value=8

## Используем метод __sub__ с функцией print
print(f'{(a-b).value=}')  ## Вывод: (a-b).value=2

## Используем метод __mul__ с функцией print
print(f'{(a*b).value=}')  ## Вывод: (a*b).value=15

## Используем метод __truediv__ с функцией print
print(f'{(a/b).value=}')  ## Вывод: (a/b).value=1.6666666666666667

## Используем метод __floordiv__ с функцией print
print(f'{(a//b).value=}')  ## Вывод: (a//b).value=1

## Используем метод __mod__ с функцией print
print(f'{(a%b).value=}')  ## Вывод: (a%b).value=2

## Используем метод __pow__ с функцией print
print(f'{(a**b).value=}')  ## Вывод: (a**b).value=125

Затем введите следующую команду в терминале, чтобы выполнить скрипт.

python binary_example.py

Операции на месте

Операции на месте - это операции, которые изменяют значение объекта на месте, не создавая новый объект. Они обозначаются операторами присваивания с дополнением, такими как +=, -=, *=, /=, и т.д.

Если оператор на месте не определен для класса Python, то вместо этого будет использоваться двоичный оператор, когда выполняется попытка операции на месте.

В inplace_example1.py есть пример, в котором нужно заменить двоичные операторы на операторы на месте:

from number import MyNumber

## Создаем два новых объекта MyNumber
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Вывод: a.value=5, b.value=3

a += b
## Используем метод __add__ с функцией print
print(f'after a+=b: {a.value=}')  ## Вывод:after a+=b: (a+b).value=8

Для запуска примера введите следующую команду в терминале:

python inplace_example1.py

Результат показывает, что при попытке выполнить операцию += использовался метод __add__.

Затем мы реализуем операции на месте в классе MyNumber, и их поведение будет немного отличаться от соответствующих двоичных операторов.

__iadd__

Магический метод __iadd__ определяет, как должна вести себя операция сложения на месте. Когда вы используете оператор += для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __iadd__(self, other: 'MyNumber') -> 'MyNumber':
        """Добавляет значение другого экземпляра к значению экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value += other.value
        print(f'after +=: {self.value=}')
        return self

__isub__

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

    #... (предыдущий код в number.py)

    def __isub__(self, other: 'MyNumber') -> 'MyNumber':
        """Вычитает значение другого экземпляра из значения экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value -= other.value
        print(f'after -=: {self.value=}')
        return self

__imul__

Магический метод __imul__ определяет, как должна вести себя операция умножения на месте. Когда вы используете оператор *= для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __imul__(self, other: 'MyNumber') -> 'MyNumber':
        """Умножает значение экземпляра на значение другого экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value *= other.value
        print(f'after *=: {self.value=}')
        return self

__itruediv__

Магический метод __itruediv__ определяет, как должна вести себя операция деления с плавающей точкой на месте. Когда вы используете оператор /= для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __itruediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Делит значение экземпляра на значение другого экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value /= other.value
        print(f'after /=: {self.value=}')
        return self

__ifloordiv__

Магический метод __ifloordiv__ определяет, как должна вести себя операция целочисленного деления на месте. Когда вы используете оператор //= для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __ifloordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Выполняет целочисленное деление значения экземпляра на значение другого экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value //= other.value
        print(f'after //=: {self.value=}')
        return self

__imod__

Магический метод __imod__ определяет, как должна вести себя операция взятия остатка от деления на месте. Когда вы используете оператор %= для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __imod__(self, other: 'MyNumber') -> 'MyNumber':
        """Выполняет взятие остатка от деления значения экземпляра на значение другого экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value %= other.value
        print(f'after %=: {self.value=}')
        return self

__ipow__

Магический метод __ipow__ определяет, как должна вести себя операция возведения в степень на месте. Когда вы используете оператор **= для экземпляров вашего класса, вызывается этот метод.

    #... (предыдущий код в number.py)

    def __ipow__(self, other: 'MyNumber') -> 'MyNumber':
        """Возводит значение экземпляра в степень значения другого экземпляра на месте."""
        print(f'input: {self.value=}, {other.value=}')
        self.value **= other.value
        print(f'after **=: {self.value=}')
        return self

Пример: Использование операций на месте

Теперь, когда мы определили операторы на месте для нашего класса MyNumber, посмотрим, как они работают в inplace_example2.py:

from number import MyNumber

## Создаем новый объект MyNumber
a = MyNumber(13)

## Используем метод __iadd__
a += MyNumber(5)
## Вывод:
## input: self.value=13, other.value=5
## after +=: self.value=18

## Используем метод __isub__
a -= MyNumber(5)
## Вывод:
## input: self.value=18, other.value=5
## after -=: self.value=13

## Используем метод __imul__
a *= MyNumber(5)
## Вывод:
## input: self.value=13, other.value=5
## after *=: self.value=65

## Используем метод __itruediv__
a /= MyNumber(5)
## Вывод:
## input: self.value=65, other.value=5
## after /=: self.value=13.0

## Используем метод __ifloordiv__
a //= MyNumber(2)
## Вывод:
## input: self.value=13.0, other.value=2
## after //=: self.value=6.0

## Используем метод __imod__
a %= MyNumber(4)
## Вывод:
## input: self.value=6.0, other.value=4
## after %=: self.value=2.0

## Используем метод __ipow__
a **= MyNumber(3)
## Вывод:
## input: self.value=2.0, other.value=3
## after **=: self.value=8.0

Затем введите следующую команду в терминале, чтобы выполнить скрипт.

python inplace_example2.py

Резюме

В этом уроке мы изучили магические методы Python, связанные с арифметическими операциями, которые позволяют определить пользовательское поведение для своих классов при взаимодействии с разными типами арифметических операций. Мы рассмотрели унарные операторы, бинарные операторы и операции на месте, и узнали, как реализовать каждый магический метод по ходу.

Реализуя эти магические методы в своих пользовательских классах, вы можете создавать интуитивные и удобные в использовании объекты, которые работают无缝но с стандартными операциями Python. Это не только улучшает читаемость вашего кода, но и делает его более поддерживаемым и дружелюбным для пользователя.

По мере дальнейшего развития своих навыков в Python, рассмотрите возможность экспериментировать с другими магическими методами, чтобы дальнейше настроить поведение своих классов и создать еще более мощные абстракции.