В чем разница между методами __new__ и __init__ в Python?

«В чем разница между методами __new__ и __init__ в Python?» — вопрос из категории Python, который задают на 26% собеседований Data Scientist / ML Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

__new__ и __init__ — это два специальных метода, участвующих в создании экземпляра класса, но они выполняют принципиально разные задачи в процессе инстанцирования.

Метод Когда вызывается Задача Возвращаемое значение Аналог в других языках
__new__(cls, ...) Первым, в момент создания объекта. Создать и вернуть новый экземпляр (объект). Это конструктор на уровне класса. Управляет самим процессом выделения памяти. Должен вернуть новый экземпляр (обычно super().__new__(cls)). Может вернуть объект другого типа. Конструктор (new в C#, Java).
__init__(self, ...) Вторым, после __new__, если __new__ вернул экземпляр этого же класса. Инициализировать (настроить) созданный объект. Устанавливает начальные значения атрибутов. Это инициализатор. Ничего не возвращает (None). Изменяет переданный ему объект self. Инициализатор (метод __init__ или конструктор с установкой полей).

Простая аналогия:

  • __new__ — это производство пустого автомобиля на заводе.
  • __init__ — это комплектация этого автомобиля: покраска, установка сидений и руля.

Базовый пример последовательности вызовов:

class MyClass:
    def __new__(cls, *args, **kwargs):
        print(f"__new__ called. Creating instance of {cls.__name__}")
        # 1. Создаем экземпляр. Обычно это делает object.__new__
        instance = super().__new__(cls)
        return instance  # Этот объект будет передан в __init__ как `self`

    def __init__(self, value):
        print(f"__init__ called. Initializing with value={value}")
        self.value = value

obj = MyClass(42)
# Вывод:
# __new__ called. Creating instance of MyClass
# __init__ called. Initializing with value=42

Практическое применение __new__ (когда он нужен): __new__ используется редко, в основном для нетривиального управления созданием объектов:

  1. Создание неизменяемых (immutable) типов: Так как __init__ может изменить объект, для неизменяемых классов (наследников tuple, int) логику установки значений нужно поместить в __new__.
  2. Паттерн Singleton (ограничение числа экземпляров):

    
    class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            print("Creating the single instance")
            cls._instance = super().__new__(cls)
        return cls._instance  # Всегда возвращаем один и тот же объект
    
    def __init__(self):
        # Внимание: __init__ будет вызываться каждый раз при вызове Singleton()!
        print("Singleton __init__ called")

s1 = Singleton() # Создаст экземпляр, вызовет init s2 = Singleton() # Вернет существующий экземпляр, но init вызовется снова! print(s1 is s2) # True


**Важное замечание:** В Python `__init__` вызывается автоматически, если `__new__` вернул объект того же класса. Это может привести к неочевидному повторному вызову `__init__` для уже инициализированного объекта (как в Singleton выше). Поэтому `__new__` и `__init__` нужно использовать согласованно.