Зачем нужен __new__?
Скорее всего, вы знаете и используете метод __init__ в классах для инициализации полей. Однако, при создании класса ему предшествует вызов метода __new__.
Специальный метод __new__ должен вернуть экземпляр класса (создав его при необходимости), который затем будет передан первым аргументом в соотвествующий __init__. Поскольку сам __init__ получает уже готовый экземпляр и всего лишь инициализирует его поля, ничего не возвращая, он не может повлиять на тип объекта или его источник. Конечно, непосредственным созданием экземпляра занимается сам Python в глубинах object.__new__, но кастомный __new__ обычно используется в следующих сценариях:
1. Подмена типа. Пользователь создает объект одного типа, а по факту получает объект другого типа, например, более специализированный.
2. Когда нужно не создавать новый объект, а отдать уже созданный ранее. Например, синглтон создается однажды, сохраняется и используется на протяжении всего времени работы программы.
3. Замена __init__.
ℹ️ __new__ – это всегда классовый метод, но к нему НЕ нужен декоратор classmethod.
Давайте рассмотрим тривиальный пример:
class Bulka:
def __init__(self, a, b):
print(f'Bulka init {a=}, {b=}')
def __new__(cls, *args, **kwargs):
print(f'{cls=}, {args=}, {kwargs=}')
# cls=, args=(5,), kwargs={'b': 7}
return super().__new__(cls)
Bulka(5, b=7)
Из вызова Bulka(5, b=7), мы попадаем в Bulka.__new__, где cls равен Bulka, а в args и kwargs хранятся все изначально переданные аргументы. Мы просто их распечатываем на экране и возвращаемся к поведению по умолчанию, а именно вызову return super().__new__(cls). Обратите внимание, что тут мы передаем только cls, опуская все прочие параметры (версия 3.3+) Но вы должны в любом случае оставить *args, **kwargs в заголовке __new__, даже если их не используете явно внутри тела этого метода. Вместо *args, **kwargs можно указать явный список параметров с их именами, аннотациями и дефолтными значениями, если вам так удобно.
Также, можете не пытаться изменить содержимое args и kwargs. args – это вообще кортеж, он неизменяем. А изменения в kwargs не отразятся в __init__. Однако разрешено вообще не использовать __init__, а задать все поля нового экземпляра прям в __new__.
Вот пример класса вообще без __init__, на голом __new__.
class Coffee:
def __new__(cls, volume = '300ml', milk: bool = False):
obj = super().__new__(cls)
obj.volume = volume
obj.milk = milk
return obj
c = Coffee()
print(f'{c.volume=}, {c.milk=}') # c.volume='300ml', c.milk=False
Фактически __init__ экономит вам всего лишь две строки. Пример от разработчиков Python – https://github.com/python/cpython/blob/main/Lib/datetime.py#L461.
А вот пример реализации шаблона "синглтон".
class Singleton(object):
_instance = None # хранит единственный экземпляр
def __new__(cls, *args, **kwargs):
if not cls._instance:
# первый вызов – создаем реально объект
cls._instance = object.__new__(cls)
return cls._instance
print(Singleton() is Singleton()) # True
Вот еще один пример, в нем мы подменяем тип на выходе.
class Maslo:
def __init__(self):
print(f'Maslo')
class Bulka:
def __init__(self, a, b):
print(f'Bulka init {a=}, {b=}')
def __new__(cls, *args, **kwargs):
if not args and not kwargs:
# пусть если без аргументов, то масло!
return Maslo()
else:
return super().__new__(cls)
print(Bulka(5, b=7)) # Bulka init a=5, b=7
print(Bulka()) # Maslo
Хотите изучить остальные эталонные примеры использования __new__? Просто зайдите в папку lib в вашем установленном Python и выполните команду: grep -l "def __new__(" *.py Их найдется целая куча в файлах:
_py_abc.py, _pydecimal.py, _pyio.py, _threading_local.py, abc.py, codecs.py, datetime.py, enum.py, fractions.py, functools.py, pathlib.py, pstats.py, sre_constants.py, ssl.py, turtle.py, typing.py, weakref.py
Еще __new__ используется в метаклассах, но это отдельная история.