← Назад к вопросам

Зачем нужен метод __new__?

2.3 Middle🔥 151 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Метод new в Python

new — это специальный метод, который создаёт новый экземпляр класса ДО того, как вызовется init. Это метод более низкого уровня, чем init.

Разница между new и init

class MyClass:
    def __new__(cls, value):
        """Создаёт объект (до инициализации)"""
        print(f"1. __new__ вызван с cls={cls}, value={value}")
        instance = super().__new__(cls)  # Создаём объект
        return instance
    
    def __init__(self, value):
        """Инициализирует объект (после создания)"""
        print(f"2. __init__ вызван с self={self}, value={value}")
        self.value = value

obj = MyClass(42)
# Вывод:
# 1. __new__ вызван с cls=<class MyClass>, value=42
# 2. __init__ вызван с self=<__main__.MyClass object>, value=42

Порядок вызова:

  1. new создаёт пустой объект
  2. init инициализирует его атрибуты

Практическое применение new

1. Singleton паттерн (одиночка)

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            print("Создаём единственный объект")
            cls._instance = super().__new__(cls)
        else:
            print("Возвращаем существующий объект")
        return cls._instance
    
    def __init__(self):
        self.name = "Singleton"

obj1 = Singleton()  # "Создаём единственный объект"
obj2 = Singleton()  # "Возвращаем существующий объект"

print(obj1 is obj2)  # True — один и тот же объект!

2. Контроль типа возвращаемого объекта

class Shape:
    def __new__(cls, shape_type):
        if shape_type == 'circle':
            return Circle()
        elif shape_type == 'square':
            return Square()
        else:
            raise ValueError(f"Unknown shape: {shape_type}")

class Circle:
    def draw(self):
        return "Drawing circle"

class Square:
    def draw(self):
        return "Drawing square"

shape = Shape('circle')
print(shape.draw())  # "Drawing circle"
print(type(shape))   # <class Square> или <class Circle>

3. Иммутабельные объекты (tuple, str, int)

class ImmutablePoint:
    def __new__(cls, x, y):
        """Создаём иммутабельный объект с замороженными значениями"""
        instance = super().__new__(cls)
        instance.x = x
        instance.y = y
        return instance
    
    def __init__(self, x, y):
        pass  # __init__ не меняет атрибуты, они уже созданы в __new__
    
    def __setattr__(self, name, value):
        """Запретить изменение атрибутов"""
        if hasattr(self, name):
            raise AttributeError(f"Cannot modify {name}")
        super().__setattr__(name, value)

point = ImmutablePoint(1, 2)
print(point.x, point.y)  # 1 2
# point.x = 5  # AttributeError: Cannot modify x

4. Кэширование объектов (например, для числовых типов)

class CachedNumber:
    _cache = {}
    
    def __new__(cls, value):
        """Кэшируем объекты для часто используемых значений"""
        if value in cls._cache:
            print(f"Возвращаем кэшированный объект для {value}")
            return cls._cache[value]
        
        print(f"Создаём новый объект для {value}")
        instance = super().__new__(cls)
        cls._cache[value] = instance
        return instance
    
    def __init__(self, value):
        self.value = value

n1 = CachedNumber(1)  # "Создаём новый объект для 1"
n2 = CachedNumber(1)  # "Возвращаем кэшированный объект для 1"
print(n1 is n2)       # True!

5. Переопределение типа возвращаемого значения

class PythonInt(int):
    """Расширенный int, который удваивает значение"""
    def __new__(cls, value):
        # Для immutable типов (int, str, tuple) нужно передать значение в super().__new__
        instance = super().__new__(cls, value * 2)
        return instance

n = PythonInt(5)
print(n)      # 10 — удвоено в __new__!
print(type(n))  # <class PythonInt>

new vs init

Аспектnewinit
Что делаетСоздаёт объектИнициализирует объект
ВозвращаетНовый экземплярNone
ВызываетсяДо initПосле new
Использует super()super().new(cls)super().init()
Когда использоватьКонтроль созданияИнициализация атрибутов
С immutable типамиОБЯЗАТЕЛЕНОпционален

Важные детали

1. new ВСЕГДА возвращает экземпляр (или None для отмены)

class NoInstance:
    def __new__(cls):
        print("__new__ вызван")
        return None  # Отменяем создание
    
    def __init__(self):
        print("__init__ вызван")

obj = NoInstance()
print(obj)  # None (и __init__ НЕ вызовется!)

2. new СТАТИЧЕСКИЙ метод (по сути)

class MyClass:
    def __new__(cls):  # cls — это ключевой параметр, не self!
        return super().__new__(cls)

3. Если new вернёт другой класс, init не вызовется

class Parent:
    def __new__(cls):
        print(f"__new__ создаёт {cls}")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__ инициализирует Parent")

class Child(Parent):
    pass

class Other:
    def __init__(self):
        print("__init__ инициализирует Other")

class Tricky(Parent):
    def __new__(cls):
        return Other()  # Возвращаем OTHER тип!
    
    def __init__(self):
        print("__init__ Tricky (не вызовется)")

obj = Tricky()
# Вывод:
# __new__ создаёт Tricky
# __init__ инициализирует Parent (вызывается, но на объекте Other!)

Когда использовать new

Используй new для:

  • Singleton паттерна
  • Контроля типа возвращаемого объекта
  • Иммутабельных классов
  • Кэширования объектов
  • Работы с immutable типами (int, str, tuple)

НЕ используй new для:

  • Обычной инициализации атрибутов (используй init)
  • Простых операций (усложняет код)
  • Если не уверен (это низкоуровневая магия)

Реальный пример: собственный класс конфига

class Config:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        if not hasattr(self, 'loaded'):
            self.loaded = True
            self.db_url = "postgresql://localhost:5432/mydb"
            self.debug = True

config1 = Config()
config2 = Config()
print(config1 is config2)  # True — один объект на весь приложение

Итог

new — низкоуровневый метод для контроля СОЗДАНИЯ объектов:

  • Вызывается ДО init
  • Возвращает экземпляр класса
  • Нужен для singleton, иммутабельных типов, кэширования
  • В 95% случаев достаточно init — используй new только если точно знаешь зачем