Установка имени класса декларативно
Почему вы не можете переопределить имя класса декларативно, например, чтобы использовать имя класса, которое не является допустимым идентификатором?
>>> class Potato:
... __name__ = 'not Potato'
...
>>> Potato.__name__ # doesn't stick
'Potato'
>>> Potato().__name__ # .. but it's in the dict
'not Potato'
Я подумал, что, возможно, это просто случай, когда это было перезаписано после завершения блока определения класса. Но, похоже, это не так, потому что имя доступно для записи, но, по-видимому, не установлено в классе dict:
>>> Potato.__name__ = 'no really, not Potato'
>>> Potato.__name__ # works
'no really, not Potato'
>>> Potato().__name__ # but instances resolve it somewhere else
'not Potato'
>>> Potato.__dict__
mappingproxy({'__module__': '__main__',
'__name__': 'not Potato', # <--- setattr didn't change that
'__dict__': <attribute '__dict__' of 'no really, not Potato' objects>,
'__weakref__': <attribute '__weakref__' of 'no really, not Potato' objects>,
'__doc__': None})
>>> # the super proxy doesn't find it (unless it's intentionally hiding it..?)
>>> super(Potato).__name__
AttributeError: 'super' object has no attribute '__name__'
Вопросы:
- Где
Potato.__name__
решается? - Как обрабатывается
Potato.__name__ = other
(внутри и вне класса блок определения)?
1 ответ:
Большинство документированных методов и атрибутов dunder на самом деле существуют в машинном коде объекта. В случае CPython они задаются как указатели в слоте в структуре C, определенной в объектной модели. (определяется здесь - https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Include/object.h#L346, но с полями легче визуализировать, когда фактически создается новый класс В C, как здесь: https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Objects/typeobject.c#L7778, где определен тип" супер")Где
Potato.__name__
решается?Следовательно,
__name__
задается там кодом вtype.__new__
, для которого он является первым параметром.Как обрабатывается
Potato.__name__
= other (внутри и вне блока определения класса)?Параметр класса
__dict__
не является простым словарем - это специальный прокси-объект отображения, и причиной этого является именно так, чтобы все настройки атрибутов в самом классе не проходили через__dict__
, а вместо этого проходили через метод__setattr__
в типе. Там назначения этим щелевым методам dunder фактически заполняются в структуре C объекта C, а затем отражаются на атрибутеclass.__dict__
.Таким образом, вне блока класса,
cls.__name__
устанавливается таким образом - как это происходит после создания класса.Внутри блока класса все атрибуты и методы являются собранный в простой дикт (хотя это можно настроить). Этот dict передается в
Именно поэтомуtype.__new__
и другие методы метакласса , но, как было сказано выше, этот метод заполняет слот__name__
из явного переданного параметра__name__
, хотя он просто обновляет прокси - сервер класса__dict__
со всеми именами в dict, используемом в качестве пространства имен.Может начинаться с содержимого, отличного от того, что находится в слоте
cls.__name__
, но последующие назначения синхронизируют оба.Интересный анедокт дело в том, что три дня назад я наткнулся на некоторый код, пытающийся повторно использовать имя
__dict__
явно в теле класса, что имеет столь же загадочные побочные эффекты. Я даже задался вопросом, должен ли быть отчет об ошибке, и запросил разработчиков Python - и, как я и думал, авторитетный ответ был:...all __dunder__ names are reserved for the implementation and they should only be used according to the documentation. So, indeed, it's not illegal, but you are not guaranteed that anything works, either.
(G. van Rossum)
И это относится точно так же к попытке определить
__name__
в теле класса.Https://mail.python.org/pipermail/python-dev/2018-April/152689.html
И если кто-то действительно хочет переопределить
__name__
как атрибут в classbody, метакласс для этого прост, как метакласс может быть:class M(type): def __new__(metacls, name, bases, namespace, **kw): name = namespace.get("__name__", name) return super().__new__(metacls, name, bases, namespace, **kw)