Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Метод new в Python
__new__ — это один из самых важных и часто неправильно понимаемых методов в Python. Это статический метод, который отвечает за создание нового экземпляра класса, в то время как __init__ отвечает за его инициализацию.
Разница между new и init
__new__ — создаёт объект (allocation)
__init__ — инициализирует объект (initialization)
class Person:
def __new__(cls, name):
# 1. Создаём объект
print(f"__new__ вызван для {cls.__name__}")
instance = super().__new__(cls) # Выделяем память
return instance
def __init__(self, name):
# 2. Инициализируем объект
print(f"__init__ вызван для {self}")
self.name = name
# Вызов:
p = Person("Alice")
# Вывод:
# __new__ вызван для Person
# __init__ вызван для <__main__.Person object at 0x...>
Порядок выполнения:
Person("Alice")
↓
__new__(Person, "Alice") ← создание объекта
↓
Возвращает instance
↓
__init__(instance, "Alice") ← инициализация
↓
Возвращает None (всегда)
↓
Возвращает instance пользователю
Зачем нужен new?
1. Контроль создания экземпляров (Singleton паттерн)
Singleton — это паттерн, при котором класс имеет только один экземпляр:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
print(f"Создан новый экземпляр {cls.__name__}")
else:
print(f"Возвращён существующий экземпляр {cls.__name__}")
return cls._instance
# Использование:
s1 = Singleton() # Создан новый экземпляр Singleton
s2 = Singleton() # Возвращён существующий экземпляр Singleton
print(s1 is s2) # True
Применение: конфигурация приложения, логгер, кэш, connection pool:
class DatabaseConnection(Singleton):
def __init__(self):
if not hasattr(self, 'connected'):
self.connected = True
print("Подключение к БД...")
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True — одно подключение на всё приложение
2. Контроль типа создаваемого объекта (Factory паттерн)
Можно возвращать разные типы объектов в зависимости от параметров:
class Animal:
def __new__(cls, animal_type):
if animal_type == 'dog':
obj = super().__new__(Dog)
obj.__class__ = Dog
elif animal_type == 'cat':
obj = super().__new__(Cat)
obj.__class__ = Cat
else:
obj = super().__new__(cls)
return obj
def __init__(self, animal_type):
self.type = animal_type
class Dog(Animal):
def sound(self):
return "Вау!"
class Cat(Animal):
def sound(self):
return "Мяу!"
# Использование:
dog = Animal('dog')
cat = Animal('cat')
print(dog.sound()) # Вау!
print(cat.sound()) # Мяу!
3. Работа с неизменяемыми типами (Immutable)
Для неизменяемых типов (int, str, tuple) нельзя использовать только __init__ — нужен __new__:
class PositiveInt(int):
"""Целое число, которое не может быть отрицательным"""
def __new__(cls, value):
if value < 0:
raise ValueError("PositiveInt не может быть отрицательным")
return super().__new__(cls, value)
# Использование:
pos = PositiveInt(42)
print(pos) # 42
try:
neg = PositiveInt(-5)
except ValueError as e:
print(f"Ошибка: {e}") # Ошибка: PositiveInt не может быть отрицательным
Другой пример — кэширование для неизменяемых объектов:
class Fraction:
"""Дробь с кэшированием для экономии памяти"""
_cache = {}
def __new__(cls, numerator, denominator):
# Получаем сокращённый вид дроби
from math import gcd
g = gcd(numerator, denominator)
key = (numerator // g, denominator // g)
# Проверяем кэш
if key not in cls._cache:
obj = super().__new__(cls)
obj.num = key[0]
obj.den = key[1]
cls._cache[key] = obj
return cls._cache[key]
def __repr__(self):
return f"{self.num}/{self.den}"
# Использование:
f1 = Fraction(2, 4) # 1/2
f2 = Fraction(2, 4) # 1/2
print(f1 is f2) # True — один объект из кэша
4. Валидация параметров перед созданием объекта
class User:
def __new__(cls, email, age):
# Валидируем перед созданием объекта
if not isinstance(email, str) or '@' not in email:
raise ValueError(f"Invalid email: {email}")
if not isinstance(age, int) or age < 0 or age > 150:
raise ValueError(f"Invalid age: {age}")
# Если валидация пройдена, создаём объект
return super().__new__(cls)
def __init__(self, email, age):
self.email = email
self.age = age
# Использование:
try:
user = User("not-an-email", 25)
except ValueError as e:
print(f"Ошибка: {e}") # Ошибка: Invalid email: not-an-email
user = User("alice@example.com", 25) # OK
Типичные ошибки с new
Ошибка 1: Забыть вернуть объект
# ❌ ПЛОХО
class Broken:
def __new__(cls):
obj = super().__new__(cls)
# Забыли вернуть!
# return obj
b = Broken() # TypeError: __new__() returned non-instance of type 'Broken'
Ошибка 2: Вернуть другой класс, забыв инициализировать его
# ❌ ПЛОХО
class Animal:
def __new__(cls, type_):
if type_ == 'dog':
return Dog() # Возвращаем Dog, но её __init__ не будет вызван
class Dog:
def __init__(self):
self.sound = "Вау!"
dog = Animal('dog') # Dog создан, но __init__ не вызван
print(dog.sound) # AttributeError: 'Dog' object has no attribute 'sound'
Исправленный вариант:
# ✅ ХОРОШО
class Animal:
def __new__(cls, type_):
if type_ == 'dog':
obj = super().__new__(Dog)
return obj
return super().__new__(cls)
def __init__(self, type_):
self.type = type_
class Dog(Animal):
def __init__(self, type_='dog'):
super().__init__(type_)
self.sound = "Вау!"
dog = Animal('dog')
print(dog.sound) # Вау!
Ошибка 3: Передавать аргументы неправильно
# ❌ ПЛОХО
class Example:
def __new__(cls, value):
print(f"__new__ получил {value}")
return super().__new__(cls)
def __init__(self, value):
print(f"__init__ получил {value}")
self.value = value
obj = Example(42)
# __new__ получил 42
# __init__ получил 42
Это работает, потому что Python автоматически передаёт аргументы в оба метода. Но лучше быть явным:
# ✅ ХОРОШО
class Example:
def __new__(cls, value):
if value < 0:
raise ValueError("value must be positive")
return super().__new__(cls)
def __init__(self, value):
self.value = value
Реальные примеры использования
Пример 1: Логирование создания объектов
import logging
logger = logging.getLogger(__name__)
class TrackedObject:
_count = 0
def __new__(cls, name):
cls._count += 1
logger.info(f"Creating instance #{cls._count}: {name}")
return super().__new__(cls)
def __init__(self, name):
self.name = name
obj1 = TrackedObject("first") # Creating instance #1: first
obj2 = TrackedObject("second") # Creating instance #2: second
Пример 2: Метаклассы (продвинутое использование)
class SingletonMeta(type):
"""Метакласс для создания Singleton классов"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class DatabaseConnection(metaclass=SingletonMeta):
def __init__(self):
self.connected = True
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
Когда использовать new?
✅ Используй new когда:
- Нужен контроль над созданием экземпляра
- Нужно вернуть другой тип объекта
- Работаешь с неизменяемыми типами
- Нужно кэшировать экземпляры
- Нужна валидация перед созданием
❌ Не используй new когда:
- Просто нужно инициализировать поля (используй
__init__) - Это усложнит код без выгоды
- Есть более простой способ (например, factory функция)
Вывод
__new__ — это мощный инструмент для контроля создания объектов. Но с большой силой приходит большая ответственность. Используй его когда действительно нужен такой контроль, но помни что это может запутать будущих читателей кода. Если есть выбор — выбирай простоту.