Dunder методи (скорочення від Double Underscore), також відомі як “магічні методи” або “спеціальні методи”, є однією з корисних можливостей Python. Імена цих методів мають подвійні підкреслення навколо (наприклад __init__, __str__, __getitem__), та дозволяють налаштовувати ваші класи під власні потреби.

Dunder методи – це спеціальні методи, які Python автоматично викликає під час виконання певних операцій над об’єктами (такі як створення нового екземпляра класу, друк об’єкта, виконання арифметичних операцій, та інше). Перевизначаючи або налаштовуючи ці методи у класі, ви можете керувати поведінкою ваших об’єктів у різних контекстах, розширивши таким чином поведінку вбудованих операцій Python, відповідно до ваших конкретних потреб.

Далі буде розглянуто деякі dunder методи з прикладами використання:

  1. __init__
  2. __new__
  3. __str__
  4. __repr__
  5. __add__
  6. _getitem__, __setitem__ та __len__
  7. __call__
  8. __enter__ та __exit__
  9. __del__
  10. __eq__
  11. __iter__ та __next__

__init__

Метод __init__ у Python є спеціальним методом класу, який виконує роль конструктора. Його основне завдання – ініціалізація нового екземпляра класу під час його створення (тобто задання початкового стану об’єкта, наприклад ініціалізація його атрибутів). Таким чином, метод __init__ надає можливість контролювати процес налаштування об’єктів класу.

Приклад:

class Rectangle:
    def __init__(self, width, height):
        if width <= 0 or height <= 0:
            raise ValueError('Width and height must be greater than zero')
        self.width = width
        self.height = height


rectangle_one = Rectangle(5, 10)
print(rectangle_one.width)
# 5

rectangle_two = Rectangle(0, 10)
# ValueError: Width and height must be greater than zero

Також слід додати, що __init__ не створює об’єкт, цим займається метод __new__. Метод __init__ автоматично викликається вже після створення об’єкту.

__new__

Метод __new__ у Python відповідає за створення нового екземпляра класу. Він автоматично викликається перед методом __init__. Це може бути корисним:

  • коли є потреба контролювати процес створення об’єкта класу
  • під час реалізації таких шаблонів програмування як Singleton
  • коли потрібно перевіряти певні умови перед створенням екземпляра класу

Приклад реалізації шаблону Singleton:

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance


obj1 = Singleton()
obj2 = Singleton()

print(obj1 is obj2)
# Виведе True, оскільки це один і той самий об'єкт

У цьому прикладі:

  • передбачається що змінна _instance зберігає посилання на єдиний екземпляр класу
  • у методі __new__, після перевірки змінної _instance на None, якщо екземпляр класу ще не створений, викликається super().__new__(cls) для його створення
  • після створення екземпляра класу, на кожний виклик Singleton(), буде повертатись вже збережене посилання на існуючий екземпляр

__str__

Метод __str__ забезпечує зрозуміле для людини рядкове представлення об’єкта. Це корисно для налагодження, ведення журналів (легування) та друку об’єктів.

Приклад:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Person name: {self.name}, age: {self.age}'


person = Person('Alice', 30)
print(person)
# Person name: Alice, age: 30

Якщо прибрати власну реалізацію методу __str__ у класі Person, то під час друку об’єкта буде використано реалізацію за замовчуванням, котра поверне погано зрозумілий результат:

<__main__.Person object at 0x00000258AAF24F40>

__repr__

Метод __repr__ у Python слугує для отримання так званого машиночитабельного строкового представлення об’єкта, яке можна використати для його відтворення. Тобто він повертає рядок, котрий точно описує внутрішню структуру об’єкта. Це може бути корисним для налагодження, документації, тощо.

Приклад:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point(x={self.x}, y={self.y})'


point = Point(3, 4)
print(repr(point))
# Point(x=3, y=4)

Як бачимо, метод __repr__ повертає рядок, який можна використати для повторного створення потрібного об’єкта класу Point.

__add__

Метод __add__ дозволяє визначити поведінку об’єктів класу при застосуванні до них оператора +, та є прикладом перевантаження операторів.

Приклад:

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

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f'Vector({self.x}, {self.y})'


v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)  # Vector(4, 6)

__getitem__, __setitem__ та __len__

Ці dunder методи дозволяють працювати з об’єктами користувацьких класів як з контейнерами (список, словник, тощо), використовуючи синтаксис доступу по індексу або ключу (obj[key]), а також обчислювати їх довжину.

Приклад:

class CustomList:
    def __init__(self, data=None):
        self.data = data if data else []

    def __getitem__(self, index):
        return self.data[index]

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

    def __len__(self):
        return len(self.data)


custom_list = CustomList([10, 20, 30])

# Використання __getitem__
print(custom_list[1])  # 20

# Використання __setitem__
custom_list[1] = 50
print(custom_list[1])  # 50

# Використання __len__
print(len(custom_list))  # 3

__call__

Визначення методу __call__ у класі, робить його екземпляри доступними для виклику (як звичайної функції).

Приклад:

class Greeter:
    def __call__(self, name):
        return f'Hello, {name}!'


greet = Greeter()
print(greet('Alice'))
# Hello, Alice!

__enter__ та __exit__

Методи __enter__ та __exit__ у Python використовуються для реалізації менеджерів контексту, котрі дозволяють автоматизувати роботу з ресурсами (такими як файли, з’єднання з базою даних, тощо). Ці методи забезпечують виконання певної логіки при вході та виході з блоку with:

  • __enter__ виконується напочатку блоку with і повертає ресурс
  • __exit__ виконується після виходу з блоку with, та звільняє ресурс (навіть якщо у блоці стався виняток)

Приклад:

class FileManager:
    def __init__(self, filename, mode):
        self.file = open(filename, mode)

    def __enter__(self):
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        self.file.close()


# Using
with FileManager('example.txt', 'w') as file:
    file.write('Hello, world!')

__del__

Метод __del__ у Python викликається під час знищення об’єкта. Він використовується для виконання додаткових дій, перед видаленням об’єкта (наприклад закриття ресурсів, очищення даних, тощо), забезпечуючи таким чином коректне завершення роботи об’єкта.

Приклад:

class Resource:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print(f'Resource {self.name} has been deleted.')


resource = Resource('File')
del resource # Resource File has been deleted.

__eq__

Метод __eq__ у Python визначає поведінку оператора == для об’єктів класу. Його використовують щоб задати власну логіку, для порівняння об’єктів класу на рівність.

Приклад:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return False


a = Point(1, 2)
b = Point(1, 2)
c = Point(2, 3)

print(a == b)  # True
print(a == c)  # False

__iter__ та __next__

Методи __iter__ та __next__ у Python використовуються для створення ітераторів – об’єктів, які можна перебирати за допомогою циклу for або інших ітераційних конструкцій:

  • __iter__ повертає сам ітератор (зазвичай self)
  • __next__ повертає наступний елемент послідовності, або генерує виняток StopIteration, якщо елементи закінчились

Приклад:

class Counter:
    def __init__(self, limit):
        self.current = 0
        self.limit = limit

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < self.limit:
            self.current += 1
            return self.current
        raise StopIteration


counter = Counter(3)
for number in counter:
    print(number)  # 1, 2, 3

Інші dunder методи

__lt__, __gt__: перевизначають операції порівняння для об’єктів класу, а саме менше (<) та більше (>).

__hash__: визначає спосіб хешування об’єкта, що може бути корисним, наприклад для використання об’єктів в якості ключів словника.

__bool__: визначає логічне значення об’єкта (True або False), що може знадобитись при використанні об’єктів класу з операторами if, while, тощо.


Мітки: