Методы dunder (double underscore) или методы двойного подчеркивания — специальные методы в языке программирования Python, которые содержат по два символа подчеркивания в начале и в конце своего названия. Цель подобного наименования — предотвращение конфликта имен с другими пользовательскими функциями.
Каждый dunder-метод связан с соответствующей языковой конструкцией Python, которая выполняет специфическую операцию по преобразованию данных.
Например, вот несколько часто используемых dunder-методов:
- 
__init__(): Инициализирует экземпляр класса, тем самым выступая в роли конструктора.
- 
__repr__(): Возвращает репрезентативное значение переменной в формате выражения Python.
- 
__eq__(): Производит сравнение двух переменных.
Каждый раз, когда интерпретатор Python встречает любую синтаксическую конструкцию, он неявно вызывает соответствующий этой конструкции dunder-метод со всеми необходимым аргументами.
Например, когда Python натыкается на знак сложения в выражении a + b, он неявно вызывает dunder-метод a.__add__(b), внутри которого  выполняется операция сложения.
Таким образом, методы с двойным подчеркиванием реализуют базовые механики языка Python. А самое главное — не только интерпретатор, но и обычный пользователь имеет произвольный доступ к ним. Более того, реализацию каждого из dunder-методов можно переопределить внутри пользовательских классов.
В этом руководстве мы рассмотрим все существующие dunder-методы языка Python и покажем примеры их использования.
Демонстрируемые скрипты запускались с помощью интерпретатора Python версии 3.10.12, установленном на облачном сервере Timeweb Cloud под управлением операционной системы Ubuntu 22.04.
Каждый скрипт следует размещать в отдельном файле с расширением .py (например, some_script.py), после чего он будет целиком доступен для выполнения с помощью команды:
python some_script.pyСоздание, инициализация и удаление
Создание, инициализация и удаление — основные этапы жизненного цикла объекта в языке Python. Каждому из таких этапов соответствует определенный dunder-метод.
| Синтаксис | Dunder-метод | Результат | Описание | 
| a = C(b, c) | C.__new__(b, c) | C | Создание | 
| a = C(b, c) | C.__init__(b, c) | None | Инициализация | 
| del a | a.__del__() | None | Удаление | 
При этом общий алгоритм функционирования этих методов имеет свои особенности:
- 
Создание. Вызывается метод __new__()с набором аргументов. Первый — класс объекта (именно класс, а не сам объект), имя которого не регламентировано и может быть любым. Все остальные — параметры, указанные при создании объекта в вызывающем коде. По итогу метод__new__()должен вернуть экземпляр класса — новый объект.
- 
Инициализация. Сразу после возврата нового объекта из метода __new__()автоматически вызывается метод__init__(), внутри которого выполняется инициализация созданного объекта. Первым аргументом передается сам объект, а всеми остальными — параметры, указанные при создании объекта. При этом имя первого аргумента регламентировано — им является ключевое словоself.
- 
Удаление. Явное удаление объекта с помощью ключевого слово delсопровождается вызовом метода__del__(). Его единственный аргумент — сам объект, доступ к которому осуществляется через ключевое слово self.
Благодаря возможности переопределения dunder-методов, отвечающих за жизненный цикл объекта, можно создавать уникальную реализацию пользовательских классов:
class Initable:
	instances = 0 # переменная класса, но не объекта
	def __new__(cls, first, second):
		print(cls.instances)
		cls.instances += 1
		return super().__new__(cls) # вызов метода создания объекта базового класса с именем текущего класса в качестве аргумента
	def __init__(self, first, second):
		self.first = first # переменная объекта
		self.second = second # еще одна переменная объекта
	def __del__(self):
		print("Аннигилирован!")
inited = Initable("Инициализируемый", 13) # вывод: 0
print(inited.first) # вывод: Инициализируемый
print(inited.second) # вывод: 13
del inited # вывод: Аннигилирован!
Благодаря подобным методам-хукам можно управлять не только внутренним состоянием объекта, но и ресурсами вне него.
Сравнение
Созданные объекты в языке Python можно сравнивать между собой, получая положительный или отрицательный результат. Каждый оператор сравнения ассоциирован со своим dunder-методом.
| Синтаксис | Dunder-метод | Результат | Описание | 
| a == b или a is b | a.__eq__(b) | bool | Равно | 
| a != b | a.__ne__(b) | bool | Неравно | 
| a > b | a.__gt__(b) | bool | Больше | 
| a < b | a.__lt__(b) | bool | Меньше | 
| a >= b | a.__ge__(b) | bool | Больше или равно | 
| a <= b | a.__le__(b) | bool | Меньше или равно | 
| hash(a) | a.__hash__() | int | Хеширование | 
В некоторых случаях язык Python предлагает несколько синтаксических конструкций для одних тех же операций сравнения. При этом каждую из таких операций можно заменить на соответствующий ей dunder-метод:
a = 5
b = 6
c = "Это самая обыкновенная строка"
print(a == b) # вывод: False
print(a is b) # вывод: False
print(a.__eq__(b)) # вывод: False
print(a != b) # вывод: True
print(a is not b) # вывод: True
print(a.__ne__(b)) # вывод: True
print(not a.__eq__(b)) # вывод: True
print(a > b) # вывод: False
print(a < b) # вывод: True
print(a >= b) # вывод: False
print(a <= b) # вывод: True
print(a.__gt__(b)) # вывод: False
print(a.__lt__(b)) # вывод: True
print(a.__ge__(b)) # вывод: False
print(a.__le__(b)) # вывод: True
print(hash(a)) # вывод: 5
print(a.__hash__()) # вывод: 5
print(c.__hash__()) # вывод: 1745008793
Метод __ne__() возвращает инвертированный результат метода __eq__(). По этой причине зачастую нет никакой необходимости переопределения __ne__(), так как основная логика сравнения как правило реализуется в __eq__().
Также важно понимать, что некоторые операции сравнения, неявно выполняемые Python во время манипуляций с элементами списков, требуют вычисления хеша. Для этого в языке есть специальный dunder-метод __hash__().
По умолчанию любой пользовательский класс уже реализует методы __eq__(), __ne__() и __hash__():
class Comparable:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
c1 = Comparable(4, 3)
c2 = Comparable(7, 9)
print(c1 == c1) # вывод: True
print(c1 != c1) # вывод: False
print(c1 == c2) # вывод: False
print(c1 != c2) # вывод: True
print(c1.__hash__()) # вывод (примерный): -2146408067
print(c2.__hash__()) # вывод (примерный): 1076316
В этом случае стандартный метод __eq__() сравнивает экземпляры без учета их внутренних переменных, созданных в конструкторе __init__(). Впрочем, тоже самое касается и метода __hash__(), значения которого отличаются от вызова к вызову.
Механика языка Python устроена таким образом, что переопределение метода __eq__() автоматически удаляет стандартный метод __hash__():
class Comparable:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
	def __eq__(self, other):
		if isinstance(other, self.__class__):
			if self.value1 == other.value1:
				return self.value2 == other.value2
			else:
				return False
		else:
			return False
c1 = Comparable(4, 3)
c2 = Comparable(7, 9)
print(c1 == c1) # вывод: True
print(c1 != c1) # вывод: False
print(c1 == c2) # вывод: False
print(c1 != c2) # вывод: True
print(c1.__hash__()) # вывод: ОШИБКА (метод не определен)
print(c2.__hash__()) # вывод: ОШИБКА (метод не определен)
Поэтому переопределение метода __eq__() требует также переопределения метода __hash__() вместе с реализацией нового алгоритма хеширования:
class Comparable:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
	def __eq__(self, other):
		if isinstance(other, self.__class__):
			if self.value1 == other.value1:
				return self.value2 == other.value2
			else:
				return False
		else:
			return False
	def __ne__(self, other):
		return not self.__eq__(other)
	def __gt__(self, other):
		return self.value1 + self.value2 > other.value1 + other.value2
	def __lt__(self, other):
		return not self.__gt__(other)
	def __ge__(self, other):
		return self.value1 + self.value2 >= other.value1 + other.value2
	def __le__(self, other):
		return self.value1 + self.value2 <= other.value1 + other.value2
	def __hash__(self):
		return hash((self.value1, self.value2)) # возвращает хэш кортежа из двух чисел
c1 = Comparable(4, 3)
c2 = Comparable(7, 9)
print(c1 == c1) # вывод: True
print(c1 != c1) # вывод: False
print(c1 == c2) # вывод: False
print(c1 != c2) # вывод: True
print(c1 > c2) # вывод: False
print(c1 < c2) # вывод: True
print(c1 >= c2) # вывод: False
print(c1 <= c2) # вывод: True
print(c1.__hash__()) # вывод: -1441980059
print(c2.__hash__()) # вывод: -2113571365
Таким образом, переопределение методов сравнения позволяет использовать стандартные синтаксические конструкции, подобно встроенным типам данным, с пользовательскими классами в независимости от сложности их внутренней реализации.
vds
Конвертация
В языке Python все встроенные типы могут быть сконвертированы один в другой. Аналогичную конвертацию можно добавить и в пользовательский класс с учетом специфики его внутренней реализации.
| Синтаксис | Dunder-метод | Результат | Описание | 
| str(a) | a.__str__() | str | Строка | 
| bool(a) | a.__bool__() | bool | Булев | 
| int(a) | a.__int__() | int | Целое число | 
| float(a) | a.__float__() | float | Вещественное число | 
| bytes(a) | a.__bytes__() | bytes | Последовательность байтов | 
| complex(a) | a.__complex__() | complex | Комплексное число | 
По умолчанию каждый пользовательский класс может быть конвертирован лишь в несколько переменных встроенного типа данных:
class Convertible:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
someVariable = Convertible(4, 3)
print(str(someVariable)) # вывод (примерный): <__main__.Convertible object at 0x1229620>
print(bool(someVariable)) # вывод: True
Однако с помощью переопределения соответствующих dunder-методов можно реализовать конвертацию пользовательского класса в любой встроенный тип данных:
class Convertible:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
	def __str__(self):
		return str(self.value1) + str(self.value2)
	def __bool__(self):
		return self.value1 == self.value2
	def __int__(self):
		return self.value1 + self.value2
	def __float__(self):
		return float(self.value1) + float(self.value2)
	def __bytes__(self):
		return bytes(self.value1) + bytes(self.value2)
	def __complex__(self):
		return complex(self.value1) + complex(self.value2)
someVariable = Convertible(4, 3)
print(str(someVariable)) # вывод: 43
print(bool(someVariable)) # вывод: False
print(int(someVariable)) # вывод: 7
print(float(someVariable)) # вывод: 7.0
print(bytes(someVariable)) # вывод: b'\x00\x00\x00\x00\x00\x00\x00'
print(complex(someVariable)) # вывод: (7+0j)
Таким образом, реализация dunder-методов конвертации позволяет объектам пользовательских классов вести себя как встроенный тип данных, тем самым расширяя свою полноту и универсальность.
Управление элементами
Любой пользовательский класс, по аналогии со списками, можно сделать итерируемым. Для этого в языке Python есть соответствующие dunder-методы для извлечения и установки элементов.
| Синтаксис | Dunder-метод | Описание | 
| len(a) | a.__len__() | Длина | 
| iter(a) или for i in a: | a.__iter__() | Итератор | 
| a[b] | a.__getitem__(b) | Извлечение элемента | 
| a[b] | a.__missing__(b) | Извлечение несуществующего элемента словаря | 
|---|---|---|
| a[b] = c | a.__setitem__(b, c) | Установка элемента | 
| del a[b] | a.__delitem__(b) | Удаление элемента | 
| b in a | a.__contains__(b) | Проверка существования элемента | 
| reversed(a) | a.__reversed__() | Элементы в обратном порядке | 
| next(a) | a.__next__() | Извлечение следующего элемента | 
Несмотря на то, что внутренняя реализация итерируемого пользовательского класса может быть произвольной, управление его элементами будет выполняться через стандартный интерфейс контейнеров, а не какими-то специфическими методами:
class Iterable:
	def __init__(self, e1, e2, e3, e4):
		self.e1 = e1
		self.e2 = e2
		self.e3 = e3
		self.e4 = e4
		self.index = 0
	def __len__(self):
		len = 0
		if self.e1: len += 1
		if self.e2: len += 1
		if self.e3: len += 1
		if self.e4: len += 1
		return len
	def __iter__(self):
		for i in range(0, self.__len__() + 1):
			if i == 0: yield self.e1
			if i == 1: yield self.e2
			if i == 2: yield self.e3
			if i == 3: yield self.e4
	def __getitem__(self, item):
		if item == 0: return self.e1
		elif item == 1: return self.e2
		elif item == 2: return self.e3
		elif item == 3: return self.e4
		else: raise Exception("Out of range")
	def __setitem__(self, item, value):
		if item == 0: self.e1 = value
		elif item == 1: self.e2 = value
		elif item == 2: self.e3 = value
		elif item == 3: self.e4 = value
		else: raise Exception("Out of range")
	def __delitem__(self, item):
		if item == 0: self.e1 = None
		elif item == 1: self.e2 = None
		elif item == 2: self.e3 = None
		elif item == 3: self.e4 = None
		else: raise Exception("Out of range")
	def __contains__(self, item):
		if self.e1 == item: return true
		elif self.e2 == item: return True
		elif self.e3 == item: return True
		elif self.e4 == item: return True
		else: return False
	def __reversed__(self):
		return Iterable(self.e4, self.e3, self.e2, self.e1)
	def __next__(self):
		if self.index >=4: self.index = 0
		if self.index == 0: element = self.e1
		if self.index == 1: element = self.e2
		if self.index == 2: element = self.e3
		if self.index == 3: element = self.e4
		self.index += 1
		return element
someContainer = Iterable(-2, 54, 6, 13)
print(someContainer.__len__()) # вывод: 4
print(someContainer[0]) # вывод: -2
print(someContainer[1]) # вывод: 54
print(someContainer[2]) # вывод: 6
print(someContainer[3]) # вывод: 13
someContainer[2] = 117
del someContainer[0]
print(someContainer[2]) # вывод: 117
for element in someContainer:
	print(element) # вывод: None, 54, 117, 13
print(117 in someContainer) # вывод: True
someContainerReversed = someContainer.__reversed__()
for element in someContainerReversed:
	print(element) # вывод: 13, 117, 54, None
print(someContainer.__next__()) # вывод: None
print(someContainer.__next__()) # вывод: 54
print(someContainer.__next__()) # вывод: 117
print(someContainer.__next__()) # вывод: 13
print(someContainer.__next__()) # вывод: None
Важно также понимать разницу между методами __iter__() и __next__(), которые позволяют выполнять итерацию объекта.
Первый итерирует объект в моменте, а второй возвращает элемент с учетом некоего внутреннего индекса.
Особый интерес также представляет метод __missing__(), который актуален только в пользовательских классах, унаследованных от базового типа словаря dict.
Благодаря этому dunder-методу можно переопределить стандартное поведение dict в момент извлечения несуществующего элемента:
class dict2(dict):
	def __missing__(self, item):
		return "Прости, но меня не существует..."
someDictionary = dict2(item1=10, item2=20, item3=30)
print(someDictionary["item1"]) # вывод: 10
print(someDictionary["item2"]) # вывод: 20
print(someDictionary["item3"]) # вывод: 30
print(someDictionary["item4"]) # вывод: Прости, но меня не существует...
Арифметические операции
Арифметические операции — самый распространенный тип манипуляций над данными. Поэтому в языке Python есть соответствующие синтаксические конструкции для выполнения сложения, вычитания, умножения и деления.
Чаще всего используются left-handed методы, выполняющие вычисления от имени правого операнда.
| Синтаксис | Dunder-метод | Описание | 
| a + b | a.__add__(b) | Сложение | 
| a - b | a.__sub__(b) | Вычитание | 
| a * b | a.__mul__(b) | Умножение | 
| a / b | a.__truediv__(b) | Деление | 
| a % b | a.__mod__(b) | Модуль | 
| a // b | a.__floordiv__(b) | Целочисленное деление | 
| a ** b | a.__pow__(b) | Степень | 
В том случае, если правый операнд не знает, как выполнить операцию, Python автоматически вызывает right-handed метод, вычисляющий значение от имени правого операнда. Однако в этом случае операнды должны быть разных типов.
| Синтаксис | Dunder-метод | Описание | 
| a + b | a.__radd__(b) | Сложение | 
| a - b | a.__rsub__(b) | Вычитание | 
| a * b | a.__rmul__(b) | Умножение | 
| a / b | a.__rtruediv__(b) | Деление | 
| a % b | a.__rmod__(b) | Модуль | 
| a // b | a.__rfloordiv__(b) | Целочисленное деление | 
| a ** b | a.__rpow__(b) | Степень | 
Также есть возможно переопределить арифметические операции, выполняемые на месте (in-place). В этом случае dunder-методы не возвращают новое значение, а изменяют переменные уже существующие левого операнда.
| Синтаксис | Dunder-метод | Описание | 
| a += b | a.__iadd__(b) | Сложение | 
| a -= b | a.__isub__(b) | Вычитание | 
| a *= b | a.__imul__(b) | Умножение | 
| a /= b | a.__itruediv__(b) | Деление | 
| a %= b | a.__imod__(b) | Модуль | 
| a //= b | a.__ifloordiv__(b) | Целочисленное деление | 
| a **= b | a.__ipow__(b) | Степень | 
Переопределяя соответствующие dunder-методы, можно задать специфическое поведение пользовательского класса во время выполнения арифметических действий:
class Arithmetic:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
	def __add__(self, other):
		return Arithmetic(self.value1 + other.value1, self.value2 + other.value2)
	def __radd__(self, other):
		return Arithmetic(other + self.value1, other + self.value2)
	def __iadd__(self, other):
		self.value1 += other.value1
		self.value2 += other.value2
		return self
	def __sub__(self, other):
		return Arithmetic(self.value1 - other.value1, self.value2 - other.value2)
	def __rsub__(self, other):
		return Arithmetic(other - self.value1, other - self.value2)
	def __isub__(self, other):
		self.value1 -= other.value1
		self.value2 -= other.value2
		return self
	def __mul__(self, other):
		return Arithmetic(self.value1 * other.value1, self.value2 * other.value2)
	def __rmul__(self, other):
		return Arithmetic(other * self.value1, other * self.value2)
	def __imul__(self, other):
		self.value1 *= other.value1
		self.value2 *= other.value2
		return self
	def __truediv__(self, other):
		return Arithmetic(self.value1 / other.value1, self.value2 / other.value2)
	def __rtruediv__(self, other):
		return Arithmetic(other / self.value1, other / self.value2)
	def __itruediv__(self, other):
		self.value1 /= other.value1
		self.value2 /= other.value2
		return self
	def __mod__(self, other):
		return Arithmetic(self.value1 % other.value1, self.value2 % other.value2)
	def __rmod__(self, other):
		return Arithmetic(other % self.value1, other % self.value2)
	def __imod__(self, other):
		self.value1 %= other.value1
		self.value2 %= other.value2
		return self
	def __floordiv__(self, other):
		return Arithmetic(self.value1 // other.value1, self.value2 // other.value2)
	def __rfloordiv__(self, other):
		return Arithmetic(other // self.value1, other // self.value2)
	def __ifloordiv__(self, other):
		self.value1 //= other.value1
		self.value2 //= other.value2
		return self
	def __pow__(self, other):
		return Arithmetic(self.value1 ** other.value1, self.value2 ** other.value2)
	def __rpow__(self, other):
		return Arithmetic(other ** self.value1, other ** self.value2)
	def __ipow__(self, other):
		self.value1 **= other.value1
		self.value2 **= other.value2
		return self
a1 = Arithmetic(4, 6)
a2 = Arithmetic(10, 3)
add = a1 + a2
sub = a1 - a2
mul = a1 * a2
truediv = a1 / a2
mod = a1 % a2
floordiv = a1 // a2
pow = a1 ** a2
radd = 50 + a1
rsub = 50 - a2
rmul = 50 * a1
rtruediv = 50 / a2
rmod = 50 % a1
rfloordiv = 50 // a2
rpow = 50 ** a2
a1 -= a2
a1 *= a2
a1 /= a2
a1 %= a2
a1 //= a2
a1 **= a2
print(add.value1, add.value2) # вывод: 14 9
print(sub.value1, sub.value2) # вывод: -6 3
print(mul.value1, mul.value2) # вывод: 40 18
print(truediv.value1, truediv.value2) # вывод: 0.4 2.0
print(mod.value1, mod.value2) # вывод: 4 0
print(floordiv.value1, floordiv.value2) # вывод: 0 2
print(pow.value1, pow.value2) # вывод: 1048576 216
print(radd.value1, radd.value2) # вывод: 54 56
print(rsub.value1, rsub.value2) # вывод: 40 47
print(rmul.value1, rmul.value2) # вывод: 200 300
print(rtruediv.value1, rtruediv.value2) # вывод: 5.0 16.666666666666668
print(rmod.value1, rmod.value2) # вывод: 2 2
print(rfloordiv.value1, rfloordiv.value2) # вывод: 5 16
print(rpow.value1, rpow.value2) # вывод: 97656250000000000 125000
В реальности именно арифметические dunder-методы переопределяются чаще всего. Поэтому хорошей практикой будет одновременная реализация как left-handed, так и right-handed методов.
Битовые операции
Помимо обычных математических операций язык Python позволяет переопределить поведение пользовательского класса во время выполнения побитовых преобразований.
| Синтаксис | Dunder-метод | Описание | 
|---|---|---|
| a & b | a.__and__(b) | Битовое И | 
| a | b | a.__or__(b) | Битовое ИЛИ | 
| a ^ b | a.__xor__(b) | Битовое исключающее ИЛИ | 
| a >> b | a.__rshift__(b) | Битовый СДВИГ вправо | 
| a << b | a.__lshift__(b) | Битовый СДВИГ влево | 
По аналогии с арифметическими операциями, побитовые преобразования можно выполнять от имени правого операнда.
| Синтаксис | Dunder-метод | Описание | 
|---|---|---|
| a & b | a.__rand__(b) | Битовое И | 
| a | b | a.__ror__(b) | Битовое ИЛИ | 
| a ^ b | a.__rxor__(b) | Битовое исключающее ИЛИ | 
| a >> b | a.__rrshift__(b) | Битовый СДВИГ вправо | 
| a << b | a.__rlshift__(b) | Битовый СДВИГ влево | 
Разумеется, побитовые операции могут выполнены на месте, изменяя значения левого операнда, а не возвращая новый объект.
| Синтаксис | Dunder-метод | Описание | 
|---|---|---|
| a &= b | a.__iand__(b) | Битовое И | 
| a |= b | a.__ior__(b) | Битовое ИЛИ | 
| a ^= b | a.__ixor__(b) | Битовое исключающее ИЛИ | 
| a >>= b | a.__irshift__(b) | Битовый СДВИГ вправо | 
| a <<= b | a.__ilshift__(b) | Битовый СДВИГ влево | 
Таким образом, любой пользовательский класс может выполнять со своим содержимым привычные битовые операции:
class Bitable:
	def __init__(self, value1, value2):
		self.value1 = value1
		self.value2 = value2
	def __and__(self, other):
		return Bitable(self.value1 & other.value1, self.value2 & other.value2)
	def __rand__(self, other):
		return Bitable(other & self.value1, other & self.value2)
	def __iand__(self, other):
		self.value1 &= other.value1
		self.value2 &= other.value2
		return self
	def __or__(self, other):
		return Bitable(self.value1 | other.value1, self.value2 | other.value2)
	def __ror__(self, other):
		return Bitable(other | self.value1, other | self.value2)
	def __ior__(self, other):
		self.value1 |= other.value1
		self.value2 |= other.value2
		return self
	def __xor__(self, other):
		return Bitable(self.value1 ^ other.value1, self.value2 ^ other.value2)
	def __rxor__(self, other):
		return Bitable(other ^ self.value1, other ^ self.value2)
	def __ixor__(self, other):
		self.value1 |= other.value1
		self.value2 |= other.value2
		return self
	def __rshift__(self, other):
		return Bitable(self.value1 >> other.value1, self.value2 >> other.value2)
	def __rrshift__(self, other):
		return Bitable(other >> self.value1, other >> self.value2)
	def __irshift__(self, other):
		self.value1 >>= other.value1
		self.value2 >>= other.value2
		return self
	def __lshift__(self, other):
		return Bitable(self.value1 << other.value1, self.value2 << other.value2)
	def __rlshift__(self, other):
		return Bitable(other << self.value1, other << self.value2)
	def __ilshift__(self, other):
		self.value1 <<= other.value1
		self.value2 <<= other.value2
		return self
b1 = Bitable(5, 3)
b2 = Bitable(7, 2)
resultAnd = b1 & b2
resultOr = b1 | b2
resultXor = b1 ^ b2
resultRshift = b1 >> b2
resultLshift = b1 << b2
resultRand = 50 & b1
resultRor = 50 | b2
resultRxor = 50 ^ b1
resultRrshift = 50 >> b2
resultRlshift = 50 << b1
b1 &= b2
b1 |= b2
b1 ^= b2
b1 >>= b2
b1 <<= b2
print(resultAnd.value1, resultAnd.value2) # вывод: 5 2
print(resultOr.value1, resultAnd.value2) # вывод: 7 2
print(resultXor.value1, resultAnd.value2) # вывод: 2 2
print(resultRshift.value1, resultAnd.value2) # вывод: 0 2
print(resultLshift.value1, resultAnd.value2) # вывод: 640 2
print(resultRand.value1, resultRand.value2) # вывод: 0 2
print(resultRor.value1, resultRor.value2) # вывод: 55 50
print(resultRxor.value1, resultRxor.value2) # вывод: 55 49
print(resultRrshift.value1, resultRrshift.value2) # вывод: 0 12
print(resultRlshift.value1, resultRlshift.value2) # вывод: 1600 400
Помимо операций с двумя операндами, есть также dunder-методы, реализующие побитовые изменения только с участием одного операнда.
| Синтаксис | Dunder-метод | Описание | 
|---|---|---|
| -a | a.__neg__() | Негация | 
| -a | a.__invert__() | Битовая инверсия | 
| +a | a.__pos__() | Битовая позитивизация | 
При этом оператор со знаком плюс в большинстве случае не влияет на значение переменной, поэтому многие классы переопределяют его для выполнения любых других функций по преобразованию объекта.
Информация об объекте
В языке Python есть несколько dunder-методов, извлекающих дополнительную информацию об объекте.
| Синтаксис | Dunder-метод | Описание | 
|---|---|---|
| str(a) | a.__str__() | Значение | 
| repr(a) | a.__repr__() | Репрезентация | 
Эти методы похожи, но различны:
- 
Метод __str__()возвращает строку со значением переменной.
- 
Метод __repr__()возвращает строку с кодом, репрезентирующим значение переменной. Его можно использовать для воссоздания оригинальной переменной в через функциюeval().
Соответственно, пользовательскому классу важно уметь предоставлять дополнительную информацию о себе:
class Human:
	def __init__(self, name, age):
		self.name = name
		self.age = age
	def __str__(self):
		return str(self.name + " (" + str(self.age) + " лет)")
	def __repr__(self):
		return "Human(" + repr(self.name) + ", " + str(self.age) + ")"
someHuman = Human("Иван", 35)
someOtherHuman = eval(repr(someHuman))
print(str(someHuman)) # вывод: Иван (35 лет)
print(repr(someHuman)) # вывод: Human('Иван', 35)
print(str(someOtherHuman)) # вывод: Иван (35 лет)
print(repr(someOtherHuman)) # вывод: Human('Иван', 35)
Размещайте Python-проекты на VDS/VPS в Timeweb Cloud
Заключение
Отличительная особенность dunder-методов — два символа подчеркивания в начале и в конце названия, предотвращающие конфликт имен с другими пользовательскими функциями.
В отличие от обычных методов управления, dunder-методы позволяют задать уникальное поведение пользовательского класса при использовании стандартных операторов Python, ответственных за:
- Арифметические операции
- Итерацию и доступ к элементам
- Создание, инициализацию и удаление объектов
Дополнительные dunder-атрибуты содержат вспомогательную информацию о программных сущностях Python, которая может облегчить реализацию пользовательских классов.
