Дандер методы python это

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

Магические (также называемые dunder как аббревиатура от двойного подчеркивания) методы в 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 ', >'.format(self) # repr(v) def __repr__(self): return 'Vector(, )'.format(self) 

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

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

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

Вызываемые типы

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

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

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

Например, если x + y , если x.__add__(y) возвращает невыполненным, 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 "", line 1, in TypeError: unsupported operand type(s) for +: 'NotAddable' and 'NotAddable' >>> y + y >>> z = x + y >>> z >>> 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 % Модульный / Остаток __mod__(self, other) a1 % a2 ** Мощность __pow__(self, other[, modulo]) a1 ** a2 > побитовое смещение вправо __rshift__(self, other) a1 >> a2 & Побитовое __and__(self, other) a1 & a2 ^ Побитовое исключающее ИЛИ __xor__(self, other) a1 ^ a2 | (Побитовое ИЛИ) __or__(self, other) a1 | a2 — Отрицание (Арифметический) __neg__(self) -a1 + Положительно __pos__(self) +a1 ~ Побитовое НЕ __invert__(self) ~a1 < Меньше чем __lt__(self, other) a1 < a2 Больше чем __gt__(self, other) a1 > a2 >= Больше или равно __ge__(self, other) a1 >= a2 [index] Оператор Индекс __getitem__(self, index) a1[index] in операторах В __contains__(self, other) a2 in a1 (*args, . ) Вызов __call__(self, *args, **kwargs) a1(*args, **kwargs)

Читайте также:  Php array search функция

Необязательный параметр по 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 

а также соответствующая версия Inplace, начиная с __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 «.format(a1) хеширования __hash__(self) hash(a1) длина __len__(self) len(a1) округление __round__(self) round(a1) Перевернутый __reversed__(self) reversed(a1) Этаж __floor__(self) math.floor(a1) Этаж __floor__(self) math.floor(a1) потолок __ceil__(self) math.ceil(a1)

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

Источник

Python. Выражения в методах и индексаторах

Если вам когда-нибудь приходилось работать с NumPy, то вы скорее всего знаете, что в индексатор массива можно передать не только индексы начала, конца, и шага. Потрясающая возможность получить срез массива по некоторому условию, в виде data[data > 0] предает массивам NumPy некоторое сходство с СУБД.

Читайте также:  Was 8 0 java support

Тут же можно вспомнить про SqlAlchemy и возможность передать в функцию filter некоторое условие для отбора записей session.query(MyModel).filter(MyModel.field == 10) .

Отличные, в общем-то возможности, не так ли? Не возникало ли у вас вопроса как они работают внутри? data > 0 и MyModel.field == 10 с точки зрения грамматики языка являются выражениями, и при передаче куда-либо Python попытается вычислить их значения. Можно даже проиллюстрировать это на простом примере:

class A: b = None def filter(*args, **kwargs): print(f"args: ") print(f"kwargs: ") filter(A.b == 1)

Результат будет следующим:

Выражение A.b == 1 было вычислено, и результат стал аргументом, что и не удивительно, однако в SqlAlchemy это как-то работает. Попробуем разобраться как.

Magic (or dunder) methods

В Python (как и в некоторых других языках) присутствует концепция т.н. «магических методов» (их еще называют dunder из-за двойных нижних подчеркиваний). Суть магических методов — предоставлять реализацию некоторого поведения объекта, при использовании этого объекта в различных контекстах. Если обратиться к достаточно заезженному, в части разъяснения аспектов ООП, примеру про класс Animal, и попытаться ответить себе на вопрос, что будет, если животное прибавить к животному — то в первую очередь следует подумать не о результате операции, а о том коде, который будет вызван при выполнении операции сложения.

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

Допустим, мы хотим сделать класс-обертку на коллекцией (над списком, для упрощения). Обертка должна иметь метод filter, принимающий некоторое условие отбора, и накладывающий это условие на оборачиваемую коллекцию. Сделаем следующее:

  1. Переопределим метод __eq__ таким образом, чтобы он возвращал не результат сравнения, а объект, хранящий информацию об операции и операндах
  2. Метод filter научим принимать в объект с такой информацией, и хранить его внутри фильтруемой коллекции

На выходе получается такой код:

class Criteria: def __init__(self, operation, argument): self.operation = operation self.argument = argument class FiltrableCollection: def __init__(self, wrapped_collection: list): self._conditions = [] self._collection = iter(wrapped_collection) def __iter__(self): return self def __next__(self): while True: element = next(self._collection) for cond in self._conditions: if cond.operation == 'eq': if element == cond.argument: return element def __eq__(self, other): return Criteria('eq', other) def filter(self, criteria): self._conditions.append(criteria) return self

Класс Criteria будет хранить информацию о типе операции сравнения, и втором операнде, а класс FiltrableCollection оборачивает коллекцию, предоставляет итератор, и выполняет фильтрацию.

Использовать FiltrableCollection можно следующим образом:

l = [1,2,1,4,1,6] filtrable_l = FiltrableCollection(l) for i in filtrable_l.filter(filtrable_l == 1): print(i)

В этом примере filtrable_l == 1 за счет возврата методом __eq__ инстанса класса Criteria, а не булева типа, передает в filter критерий отбора. Метод filter сохраняет его внутри инстанса FiltrableCollection. Далее, при проходе циклом по инстансу FiltableCollection, логика метода __next__ применяет условие к очередному элементу исходной коллекции. Но, одной эквивалентностью сыт не будешь. Можно доопределить реализацию FiltrableCollection, чтобы поддержать все условия сравнения.

class Criteria: def __init__(self, operation, argument): self.operation = operation self.argument = argument class FiltrableCollection: def __init__(self, wrapped_collection: list): self._conditions = [] self._collection = iter(wrapped_collection) def __iter__(self): return self def __next__(self): while True: element = next(self._collection) try: for cond in self._conditions: if cond.operation == 'eq': assert element == cond.argument elif cond.operation == 'ne': assert element != cond.argument elif cond.operation == 'lt': assert element < cond.argument elif cond.operation == 'le': assert element cond.argument elif cond.operation == 'ge': assert element >= cond.argument return element except AssertionError: pass def __eq__(self, other): return Criteria('eq', other) def __ne__(self, other): return Criteria('ne', other) def __lt__(self, other): return Criteria('lt', other) def __le__(self, other): return Criteria('le', other) def __gt__(self, other): return Criteria('gt', other) def __ge__(self, other): return Criteria('ge', other) def filter(self, criteria): self._conditions.append(criteria) return self

Теперь в методе filter можно использовать разные операции сравнений, и накладывать несколько условий одновременно.

l = [1,2,1,4,1,6] filtrable_l = FiltrableCollection(l) for i in filtrable_l.filter(filtrable_l >= 2).filter(filtrable_l < 6): print(i)

Условие для отбора в индексаторе делается не сложнее. За доступ к элементу по ключу отвечает магический метод __getitem__ . Метод принимает на вход в качестве аргумента ключ, по которому и производится поиск. Собственно, как и в истории со сравнением, в качестве ключа можно передать инстанс уже реализованного класса Criteia, по которому и будет проведен отбор.

def __getitem__(self, key): result = [] while True: try: element = next(self._collection) except StopIteration: break try: if key.operation == 'eq': assert element == cond.argument elif key.operation == 'ne': assert element != cond.argument elif key.operation == 'lt': assert element < cond.argument elif key.operation == 'le': assert element cond.argument elif key.operation == 'ge': assert element >= cond.argument result.append(element) except AssertionError: pass return result

Теперь для FiltrableCollection можно использовать индексатор с условием, который выдаст все подходящие элементы:

l = [1,2,1,4,1,6] filtrable_l = FiltrableCollection(l) filtrable_l[filtrable_l > 1]

Несмотря на успешную реализацию, следует учитывать алгоритмическую сложность такого подхода. Для всех примеров кода, приведенных в статье, O(N) (и это как минимум) - наш друг, товарищ, и брат.

Читайте также:  Php удаляет повторяющиеся элементы массива

References

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

Источник

Оцените статью