Поиск…


Магические / Dunder методы

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

Рассмотрим эту реализацию двумерных векторов.

import math

class Vector(object):
    # instantiation
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # unary negation (-v)
    def __neg__(self):
        return Vector(-self.x, -self.y)

    # addition (v + u)
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    # subtraction (v - u)
    def __sub__(self, other):
        return self + (-other)

    # equality (v == u)
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    # abs(v)
    def __abs__(self):
        return math.hypot(self.x, self.y)

    # str(v)
    def __str__(self):
        return '<{0.x}, {0.y}>'.format(self)

    # repr(v)
    def __repr__(self):
        return 'Vector({0.x}, {0.y})'.format(self)

Теперь можно естественным образом использовать экземпляры класса Vector в различных выражениях.

v = Vector(1, 4)
u = Vector(2, 0)

u + v           # Vector(3, 4)
print(u + v)    # "<3, 4>" (implicit string conversion)
u - v           # Vector(1, -4)
u == v          # False
u + v == v + u  # True
abs(u + v)      # 5.0

Типы контейнеров и последовательности

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

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

class sparselist(object):
    def __init__(self, size):
        self.size = size
        self.data = {}
    
    # l[index]
    def __getitem__(self, index):
        if index < 0:
            index += self.size
        if index >= self.size:
            raise IndexError(index)
        try:
            return self.data[index]
        except KeyError:
            return 0.0

    # l[index] = value
    def __setitem__(self, index, value):
        self.data[index] = value

    # del l[index]
    def __delitem__(self, index):
        if index in self.data:
            del self.data[index]

    # value in l
    def __contains__(self, value):
        return value == 0.0 or value in self.data.values()

    # len(l)
    def __len__(self):
        return self.size

    # for value in l: ...
    def __iter__(self):
        return (self[i] for i in range(self.size)) # use xrange for python2

Затем мы можем использовать sparselist , как обычный list .

l = sparselist(10 ** 6)  # list with 1 million elements
0 in l                   # True
10 in l                  # False

l[12345] = 10            
10 in l                  # True
l[12345]                 # 10

for v in l:
    pass  # 0, 0, 0, ... 10, 0, 0 ... 0

Типы вызовов

class adder(object):
    def __init__(self, first):
        self.first = first

    # a(...)
    def __call__(self, second):
        return self.first + second

add2 = adder(2)
add2(1)  # 3
add2(2)  # 4

Обработка нереализованного поведения

Если ваш класс не реализует определенный перегруженный оператор для предоставленных типов аргументов, он должен return NotImplemented ( обратите внимание, что это специальная константа , не такая же, как NotImplementedError ). Это позволит Python вернуться к другим методам, чтобы заставить работу работать:

Когда NotImplemented возвращается, интерпретатор затем попытается NotImplemented отраженную операцию на другом типе или другом резервном копировании в зависимости от оператора. Если все предпринятые операции возвращают NotImplemented , интерпретатор поднимет соответствующее исключение.

Например, если x + y , если x.__add__(y) возвращает unimplemented, вместо этого выполняется y.__radd__(x) .

class NotAddable(object):

    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        return NotImplemented


class Addable(NotAddable):

    def __add__(self, other):
        return Addable(self.value + other.value)

    __radd__ = __add__

Поскольку это отраженный метод, мы должны реализовать __add__ и __radd__ чтобы получить ожидаемое поведение во всех случаях; к счастью, поскольку они оба делают то же самое в этом простом примере, мы можем воспользоваться ярлыком.

В использовании:

>>> x = NotAddable(1)
>>> y = Addable(2)
>>> x + x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NotAddable' and 'NotAddable'
>>> y + y
<so.Addable object at 0x1095974d0>
>>> z = x + y
>>> z
<so.Addable object at 0x109597510>
>>> z.value
3

Перегрузка оператора

Ниже приведены операторы, которые могут быть перегружены в классах, а также требуемые определения методов и пример оператора, используемого в выражении.

NB Использование other в качестве имени переменной не является обязательным, но считается нормой.

оператор метод выражение
+ Дополнение __add__(self, other) a1 + a2
- Вычитание __sub__(self, other) a1 - a2
* Умножение __mul__(self, other) a1 * a2
@ Матричное умножение __matmul__(self, other) a1 @ a2 ( Python 3.5 )
/ Отдел __div__(self, other) a1 / a2 ( только для Python 2 )
/ Отдел __truediv__(self, other) a1 / a2 ( Python 3 )
// Отдел этажей __floordiv__(self, other) a1 // a2
% Modulo / Remainder __mod__(self, other) a1 % a2
** Мощность __pow__(self, other[, modulo]) a1 ** a2
<< Побитовый сдвиг влево __lshift__(self, other) a1 << a2
>> Побитовый правый сдвиг __rshift__(self, other) a1 >> a2
& Побитовое И __and__(self, other) a1 & a2
^ Побитовое XOR __xor__(self, other) a1 ^ a2
| (Побитовое ИЛИ) __or__(self, other) a1 | a2
- Отрицание (арифметика) __neg__(self) -a1
+ Положительный __pos__(self) +a1
~ Побитовое НЕ __invert__(self) ~a1
< Меньше, чем __lt__(self, other) a1 < a2
<= Меньше или равно __le__(self, other) a1 <= a2
== Равно __eq__(self, other) a1 == a2
!= Не равно __ne__(self, other) a1 != a2
> Больше, чем __gt__(self, other) a1 > a2
>= Больше или равно __ge__(self, other) a1 >= a2
[index] Оператор индекса __getitem__(self, index) a1[index]
in операторе In __contains__(self, other) a2 in a1
(*args, ...) Вызов __call__(self, *args, **kwargs) a1(*args, **kwargs)

Необязательный параметр modulo для __pow__ используется только встроенной функцией pow .


Каждый из методов, соответствующих двоичному оператору, имеет соответствующий «правый» метод, начинающийся с __r , например __radd__ :

class A:
    def __init__(self, a):
        self.a = a
    def __add__(self, other):
        return self.a + other
    def __radd__(self, other):
        print("radd")
        return other + self.a

A(1) + 2  # Out:  3
2 + A(1)  # prints radd. Out: 3

а также соответствующую версию __i , начиная с __i :

class B:
    def __init__(self, b):
        self.b = b
    def __iadd__(self, other):
        self.b += other
        print("iadd")
        return self

b = B(2)
b.b       # Out: 2
b += 1    # prints iadd
b.b       # Out: 3

Поскольку в этих методах нет ничего особенного, многие другие части языка, части стандартной библиотеки и даже сторонние модули самостоятельно добавляют магические методы, например методы для приведения объекта к типу или проверки свойств объекта. Например, встроенная функция str() вызывает метод __str__ объекта, если он существует. Некоторые из этих видов использования перечислены ниже.

функция метод выражение
Кастинг для int __int__(self) int(a1)
Абсолютная функция __abs__(self) abs(a1)
Кастинг на str __str__(self) str(a1)
Кастинг для unicode __unicode__(self) unicode(a1) (только для Python 2)
Строковое представление __repr__(self) repr(a1)
Кастинг для bool __nonzero__(self) bool(a1)
Форматирование строк __format__(self, formatstr) "Hi {:abc}".format(a1)
хеширования __hash__(self) hash(a1)
длина __len__(self) len(a1)
Перевернутый __reversed__(self) reversed(a1)
Этаж __floor__(self) math.floor(a1)
потолок __ceil__(self) math.ceil(a1)

Существуют также специальные методы __enter__ и __exit__ для менеджеров контекста и многие другие.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow