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

Что делает __new__ в Python?

2.0 Middle🔥 11 комментариев
#DevOps и инфраструктура

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

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

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

Метод 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__ — это мощный инструмент для контроля создания объектов. Но с большой силой приходит большая ответственность. Используй его когда действительно нужен такой контроль, но помни что это может запутать будущих читателей кода. Если есть выбор — выбирай простоту.