Why can't you override a class name declaratively, e.g. to use a class name which is not a valid identifier?
>>> class Potato:
... __name__ = 'not Potato'
...
>>> Potato.__name__ # doesn't stick
'Potato'
>>> Potato().__name__ # .. but it's in the dict
'not Potato'
I thought maybe it was simply a case that this was overwritten after the class definition block completes. But seems that's not true, because the name is writable yet apparently not set in the class 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__'
Questions:
Potato.__name__
resolve?Potato.__name__ = other
handled (inside and outside of a class definition block)? Where does
Potato.__name__
resolve?
Most documented dunder methods and attributes actually exist in the native code side of the object. In the case of CPython, they are set as pointers in a slot in a C Struct defined in the object model. (defined here - https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Include/object.h#L346 , but with fields easier to visualize when one actually creates a new class in C, like here: https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Objects/typeobject.c#L7778 , where the "super" type is defined)
Therefore, __name__
is set there by the code in type.__new__
, to which it is the first parameter.
How is
Potato.__name__
= other handled (inside and outside of a class definition block)?
A class's __dict__
parameter is not a plain dictionary - it is an special mapping proxy object, and the reason for that is exactly so that all attribute settings on the class itself don't go through the __dict__
, and instead go through the __setattr__
method in type. In there, assignments to these slotted dunder methods are actually filled in the C object's C structure, and then reflected on the class.__dict__
attribute.
So, outside a class block, cls.__name__
is set in this way - as it takes place after the class has been created.
Inside a class block, all attributes and methods are collected into a plain dict (though that can be customized). This dict is passed to type.__new__
and other metaclass methods - but as said above, this method fills in the __name__
slot from the explicit passed name
parameter (that is, the "name" argument passed in the call to type.__new__
)- even though it just updates the class __dict__
proxy with all names in the dict used as namespace.
That is why cls.__dict__["__name__"]
can start with a different content from what is in the cls.__name__
slot, but subsequent assignments put both in sync.
An interesting anecdote is that three days ago I came across some code trying to reuse the __dict__
name explicitly in the class body, which has similarly puzzling side-effects.
I even wondered whether there should be a bug report on that, and queried the Python developers - and as I had thought of, the authoritative answer was:
...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)
And it applies just the same to trying to define __name__
in the class body.
https://mail.python.org/pipermail/python-dev/2018-April/152689.html
And if one actually wants to override __name__
as an attribute in the classbody, a metaclass for that is a simple as a metaclass can be:
class M(type):
def __new__(metacls, name, bases, namespace, **kw):
name = namespace.get("__name__", name)
return super().__new__(metacls, name, bases, namespace, **kw)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With