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

Можно ли в __new__ отменить создание экземпляра класса?

2.4 Senior🔥 111 комментариев
#Python Core

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

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

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

Отмена создания экземпляра в new

Да, можно полностью отменить создание экземпляра класса в методе __new__, вернув что-то другое вместо объекта класса. Это мощная техника для реализации паттернов типа Singleton, Factory и объектового кэширования.

1. Как работает new

Порядок вызова методов:

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("__new__ вызван")
        instance = super().__new__(cls)  # Создаём объект
        return instance  # Возвращаем объект
    
    def __init__(self):
        print("__init__ вызван")

obj = MyClass()  # Сначала __new__, потом __init__

Вывод:

__new__ вызван
__init__ вызван

2. Возврат экземпляра другого класса

Вместо создания объекта текущего класса можно вернуть что угодно:

class A:
    def __new__(cls):
        print(f"Запрошен {cls.__name__}, но создаём B")
        return B()  # Возвращаем экземпляр другого класса

class B:
    def __init__(self):
        self.type = "B"

obj = A()  # A не будет создан!
print(type(obj))  # <class __main__.B>
print(obj.type)   # B

Важно: __init__ от A вызван не будет, так как вернулся объект класса B.

3. 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.value = None

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

Вывод:

Создаём новый экземпляр
Возвращаем существующий экземпляр
True

4. Кэширование по параметрам

Можно возвращать кэшированные объекты на основе аргументов:

class CachedObject:
    _cache = {}
    
    def __new__(cls, key: str):
        if key in cls._cache:
            print(f"Возвращаем кэшированный объект для {key}")
            return cls._cache[key]
        
        print(f"Создаём новый объект для {key}")
        instance = super().__new__(cls)
        cls._cache[key] = instance
        return instance
    
    def __init__(self, key: str):
        self.key = key
        self.created_at = datetime.now()

obj1 = CachedObject("user_1")
obj2 = CachedObject("user_1")  # Вернёт кэшированный
obj3 = CachedObject("user_2")  # Создаст новый

print(obj1 is obj2)  # True
print(obj1 is obj3)  # False

5. Factory паттерн через new

class Shape:
    def __new__(cls, shape_type: str):
        if shape_type == "circle":
            return Circle()
        elif shape_type == "square":
            return Square()
        else:
            raise ValueError(f"Неизвестный тип: {shape_type}")

class Circle:
    def draw(self):
        return "Рисуем круг"

class Square:
    def draw(self):
        return "Рисуем квадрат"

circle = Shape("circle")
square = Shape("square")
print(circle.draw())  # Рисуем круг
print(square.draw())  # Рисуем квадрат
print(type(circle))   # <class __main__.Circle>

6. Полная отмена создания (возврат None)

Можно даже вернуть None или любой другой объект:

class NoInstance:
    def __new__(cls):
        print("Экземпляр не будет создан")
        return None  # Или вообще ничего не возвращать

obj = NoInstance()
print(obj)  # None

7. Параметризованный Singleton

class Database:
    _instances = {}
    
    def __new__(cls, db_name: str):
        if db_name not in cls._instances:
            instance = super().__new__(cls)
            cls._instances[db_name] = instance
        return cls._instances[db_name]
    
    def __init__(self, db_name: str):
        self.db_name = db_name

db1 = Database("main")
db2 = Database("main")
db3 = Database("cache")

print(db1 is db2)  # True (одна база)
print(db1 is db3)  # False (разные базы)

8. Когда init вызывается после отмены?

Важный момент: __init__ вызывается ТОЛЬКО если вернулся экземпляр того же класса:

class A:
    def __new__(cls):
        instance = B()  # Другой класс
        return instance
    
    def __init__(self):
        print("__init__ A вызван")  # Не будет вызван!

class B:
    def __init__(self):
        print("__init__ B вызван")

obj = A()  # Вывод: __init__ B вызван

9. Практический пример: Immutable объект

class ImmutablePoint:
    _instances = {}
    
    def __new__(cls, x: int, y: int):
        key = (x, y)
        if key not in cls._instances:
            instance = super().__new__(cls)
            cls._instances[key] = instance
            instance.x = x
            instance.y = y
        return cls._instances[key]
    
    def __setattr__(self, name, value):
        raise AttributeError("ImmutablePoint не может быть изменена")

p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
print(p1 is p2)  # True (один объект на координаты)

Лучшие практики

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

  • Реализации Singleton паттерна
  • Фабрик, которые возвращают разные типы
  • Кэширования объектов
  • Контроля над процессом создания

❌ Избегай:

  • Сложной логики в __new__ (это запутывает код)
  • Смешивания __new__ и __init__ логики
  • Возврата случайных типов без документации

__new__ — это мощный инструмент, но используй его осторожно. В большинстве случаев проще использовать обычные фабрик-функции или классметоды.