В каких случаях и для чего в Python используется метод __new__?

Ответ

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

Это низкоуровневый метод, который используется редко, но необходим в следующих случаях:

  1. Реализация паттерна Singleton: Гарантирует, что у класса будет только один экземпляр.
  2. Наследование от неизменяемых (immutable) типов: При наследовании от таких типов, как str, int или tuple, необходимо переопределить __new__, так как изменить их после создания уже нельзя.
  3. Создание объектов-заместителей: Когда нужно вернуть экземпляр другого класса в зависимости от переданных аргументов (реализация фабричного метода).

Пример реализации Singleton:

class DatabaseConnection:
    _instance = None

    def __new__(cls, *args, **kwargs):
        # Если экземпляр еще не создан
        if not cls._instance:
            # Создаем новый экземпляр, вызывая __new__ родительского класса
            cls._instance = super().__new__(cls)
        # Возвращаем существующий или только что созданный экземпляр
        return cls._instance

    def __init__(self, dsn):
        # Этот метод будет вызываться каждый раз,
        # но на одном и том же экземпляре.
        self.dsn = dsn

# Оба вызова вернут один и тот же объект
db1 = DatabaseConnection("user:pass@host1")
db2 = DatabaseConnection("user:pass@host2")

print(id(db1) == id(db2))  # Вывод: True
print(db1.dsn)             # Вывод: user:pass@host2

Ключевое отличие от __init__:

  • __new__(cls, ...): Первый аргумент — класс cls. Создает и возвращает экземпляр. Является статическим методом.
  • __init__(self, ...): Первый аргумент — экземпляр self. Ничего не возвращает. Инициализирует уже созданный экземпляр.

Ответ 18+ 🔞

Ах ты ж ёпта, ну и тема подъехала! Про __new__ и __init__. Слушай, это ж как в строительстве, блядь: один — прораб, который заливает фундамент и ставит коробку дома, а второй — дизайнер-отделочник, который потом обои клеит и унитаз прикручивает.

Вот __new__ — это тот самый прораб, мудак скрытный. Он вызывается первым, его задача — взять чертежи (класс) и вернуть тебе голый, сырой объект. Вообще нихуя не инициализированный, просто кусок памяти. Статический метод, чистая магия, блядь.

А __init__ — это уже наш отделочник. Он получает готовую коробку (self) и начинает её обустраивать: атрибуты навешивать, значения присваивать. Ничего не возвращает, только в подаренном пространстве хозяйничает.

Зачем этот __new__ вообще, спросишь? Ну, обычно он сам всё делает под капотом, тебе париться не надо. Но бывают моменты, когда без его переопределения — просто пиздец.

  1. Singleton (Один-на-хуй экземпляр). Чтоб класс как сука упрямая плодился только в одном экземпляре.
  2. Наследование от неизменяемых типов. Ну, типа str, int, tuple. Их после создания не перепишешь, так что всю логику создания надо в __new__ засунуть, иначе нихуя не выйдет.
  3. Фабрика или подмена объекта. Когда в зависимости от аргументов нужно вернуть объект вообще другого класса. Хитрая жопа, но бывает.

Вот тебе пример, как сделать так, чтобы класс был один, как мой хуй в бане:

class DatabaseConnection:
    _instance = None  # Тут будем хранить нашу одну-единственную прелесть

    def __new__(cls, *args, **kwargs):
        # Если нашей прелести ещё нет...
        if not cls._instance:
            # То вызываем __new__ у родителя (object), чтобы создать эту самую прелесть
            cls._instance = super().__new__(cls)
        # А вот и она! Возвращаем её, даже если нас зовут сто раз.
        return cls._instance

    def __init__(self, dsn):
        # ВНИМАНИЕ, ПОДВОХ! Этот __init__ будет вызываться КАЖДЫЙ РАЗ при "создании".
        # Поэтому последний вызов перетрет dsn у нашего единственного экземпляра.
        self.dsn = dsn

# Пытаемся создать два соединения
db1 = DatabaseConnection("user:pass@host1")
db2 = DatabaseConnection("user:pass@host2")

print(id(db1) == id(db2))  # True. Это один и тот же объект, ёбана!
print(db1.dsn)             # Выведет "user:pass@host2". Потому что __init__ отработал на том же объекте второй раз и перезаписал dsn. Вот такая, блядь, особенность.

Итог, блядь:

  • __new__(cls, ...)создаёт. Возвращает объект. Вызывается раз.
  • __init__(self, ...)настраивает. Ничего не возвращает. Может вызываться на уже существующем объекте (как в синглтоне).

В 95% случаев тебе хватит __init__. Но когда полезешь в метаклассы или в наследование от tuple — вспомни про этого скрытного прораба, __new__. Он тебе, сука, жизнь спасёт.