Почему вывод программы — "B B True" в этом синглтоне

Почему вывод программы — "B B True" в этом синглтоне

@python_quiz

Разберем этот квиз

Кратко: код реализует одиночку (singleton) через переопределение __new__, но __init__ выполняется при каждом создании, поэтому поле name перезаписывается. В результате оба объекта — один и тот же экземпляр с именем из последнего вызова.

Исходный код (упорядоченный для удобства)

python

Разбор по шагам

  1. При первом вызове Service('A'):

- Python вызывает __new__(Service, 'A'). Внутри __new__ нет атрибута _inst у Service, значит выполняется правая часть выражения:
- setattr(cls, '_inst', object.__new__(cls)) — создаёт новый объект и сохраняет его в Service._inst. Функция setattr возвращает None.
- Затем используется or cls._inst, поэтому итогом выражения становится созданный экземпляр.
- __new__ возвращает этот единственный экземпляр.
- Затем вызывается Service.__init__(экземпляр, 'A'), записывается self.name = 'A'.

  1. При втором вызове Service('B'):

- __new__(Service, 'B') видит, что Service._inst уже существует, поэтому сразу возвращает этот же экземпляр.
- Python всё равно вызывает __init__ на возвращённом экземпляре (поведение CPython: если __new__ возвращает экземпляр того же класса, __init__ вызывается), и __init__ снова выполняет self.name = 'B', перезаписывая имя.

  1. Итог:

- s1 и s2 — тот же объект (s1 is s2 -> True).
- Атрибут name у этого объекта равен 'B' (последняя инициализация), значит печать выдаёт: B B True

Почему используется такой трюк в __new__?

Выражение

python

работает так:

  • getattr возвращает экземпляр, если он уже есть;
  • иначе выполняется второй операнд: setattr сохранит созданный объект в атрибут и вернёт None, поэтому None or cls._inst вернёт только что записанный экземпляр.

Это компактная (но не самая читаемая) идиома для ленивого создания единственного экземпляра.

Анализ вариантов ответов (почему какие-то неверны)

  • "A B False" — неверно, потому что __new__ гарантирует один экземпляр, значит s1 is s2 не может быть False.
  • "A B True" — неверно, потому что если s1 и s2 — один объект, то поле name будет перезаписано последним __init__ и оба будут иметь значение 'B', а не 'A B'.
  • "B B True" — совпадает с объяснением: один экземпляр, имя из последнего вызова.
  • "B A True" — неверно, порядок имён противоречит последовательности вызовов и перезаписи: второе создание задаёт 'B', оно переопределит 'A'.

Как сделать поведение более очевидным или «безопасным»?

Если цель — чтобы инициализация выполнялась только при первом создании, есть несколько подходов.

1) Защитить __init__ флагом:

python

2) Реализовать singleton через метакласс (более питонично):

python

Здесь __call__ метакласса контролирует создание: __init__ будет вызван только при первом создании, потому что при создании экземпляра мы используем super().__call__ (который вызывает __new__ и __init__) только один раз.

3) Использовать фабрику/декоратор — иногда проще и явнее, чем переопределение магических методов.

Вывод

Код реализует singleton через переопределение __new__. Поскольку __init__ вызывается при каждом вызове конструктора, состояние экземпляра может перезаписываться — это ключ к пониманию вывода программы. Если нужно, чтобы инициализация происходила только единожды, стоит явно защитить __init__ или использовать метакласс/декоратор для управления созданием экземпляра.

Report Page