Установка имени класса декларативно
Почему вы не можете переопределить имя класса декларативно, например, чтобы использовать имя класса, которое не является допустимым идентификатором?
>>> 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)