The class
object B
is of type
UpperMeta
.
This has the consequence that all classmethods of UpperMeta
are available on the class B
. The attribute is not on the class B
, but gets proxied from B
's class(B
is the class, not an instance of B
)
>>> print dir(B)
# General lack of echo()
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__metaclass__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
It's here:
>>> print dir(B.__class__)
['__abstractmethods__', '__base__', ..., 'echo', 'mro']
From the documentation:
The default behavior for attribute access is to get, set, or delete
the attribute from an object’s dictionary. For instance, a.x has a
lookup chain starting with a.dict['x'], then
type(a).dict['x'], and continuing through the base classes of
type(a) excluding metaclasses.
This is a little confusing towards the end "...excluding metaclasses..", which really means the metaclasses of type(a), therefore saying that if your metaclass UpperMeta
has a metaclass TopMeta
and TopMeta
defines sos()
, that one won't be looked up:
class TopMeta(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(TopMeta, cls).__new__(cls, clsname, bases, uppercase_attr)
def sos(cls):
return 'sos'
class UpperMeta(type):
__metaclass__ = TopMeta
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperMeta, cls).__new__(cls, clsname, bases, uppercase_attr)
class B(object):
__metaclass__ = UpperMeta
assert not hasattr(B, 'sos')
The only talk that ever explained metaclasses properly: David Beazley - Python 3 Metaprogramming. You only have the first 80 minutes or so.