Можно ли в __new__ отменить создание экземпляра класса?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Отмена создания экземпляра в 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__ — это мощный инструмент, но используй его осторожно. В большинстве случаев проще использовать обычные фабрик-функции или классметоды.