Установка имени класса декларативно


Почему вы не можете переопределить имя класса декларативно, например, чтобы использовать имя класса, которое не является допустимым идентификатором?

>>> 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__'

Вопросы:

  1. Где Potato.__name__ решается?
  2. Как обрабатывается Potato.__name__ = other (внутри и вне класса блок определения)?
1 5

1 ответ:

Где Potato.__name__ решается?

Большинство документированных методов и атрибутов 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, где определен тип" супер")

Следовательно, __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)