ПрограммированиеSenior Python разработчик

Как работает метод __new__ в Python и чем отличается от __init__? Когда и почему нужно переопределять __new__, а не __init__? Приведите подробный пример использования.

Проходите собеседования с ИИ помощником Hintsage

Ответ.

В Python создание объекта класса — двухэтапный процесс:

  1. Сначала вызывается статический метод __new__, который отвечает за выделение и возврат нового экземпляра класса (инстанса).
  2. Затем вызывается __init__, который инициализирует уже созданный объект.

Отличия:

  • __new__ — создание (можно вернуть любой новый объект; обязательный параметр — класс).
  • __init__ — инициализация (работает с self, уже выделенным экземпляром).
  • Обычно __new__ переопределяется для наследников неизменяемых типов (tuple, str, int), создания синглтонов и паттерна "фабрика".

Пример использования:

class MyStr(str): def __new__(cls, value): print("__new__ called") instance = super().__new__(cls, value.upper()) # изменяем значение до создания return instance def __init__(self, value): print("__init__ called", value) s = MyStr('abc') # __new__ called -> __init__ called abc print(s) # 'ABC'

Вопрос с подвохом.

Можно ли инициализировать неизменяемый объект через __init__, если в классе не реализован __new__?

Ответ с примером:

Нет! Например, для неизменяемых типов (как у str, int, tuple) любые изменения над полями/значением должны быть в __new__, иначе в __init__ изменить значение невозможно.

class MyTuple(tuple): def __init__(self, items): print(f'__init__! {items}') def __new__(cls, items): print(f'__new__! {items}') return super().__new__(cls, map(str, items)) t = MyTuple([1, 2, 3]) print(t) # ('1', '2', '3')

Примеры реальных ошибок из-за незнания тонкостей темы


История

В крупном проекте на Django реализовали наследование от str для хранения особых типов строк. Пытались видоизменять значение во __init__ — но результат не менялся, так как str неизменяем. Исправили только после изучения и переопределения __new__.


История

При реализации паттерна Singleton забыли реализовать логику возврата существующего инстанса в __new__: несколько вызовов класса по-прежнему создавали новые объекты, что приводило к фрагментации памяти.


История

В библиотеке для сериализации данных при кэшировании использовали класс с переопределённым __init__, но не уследили, что для кэширования нужен именно возврат одного и того же объекта через __new__. Из-за этого кэш ломался при каждом новом вызове, потому что создавались разные объекты.