Setting a class' metaclass using a decorator
decorator, metaclass, python
Solution
The class has no `__metaclass__` attribute set... because you never set it!
Which metaclass to use is normally determined by a name `__metaclass__` set in a class block. The `__metaclass__` attribute isn't set by the metaclass. So if you invoke a metaclass directly rather than setting `__metaclass__` and letting Python figure it out, then no `__metaclass__` attribute is set.
In fact, normal classes are all instances of the metaclass `type`, so if the metaclass always set the `__metaclass__` attribute on its instances then every class would have a `__metaclass__` attribute (most of them set to `type`).
I would not use your decorator approach. It obscures the fact that a metaclass is involved (and which one), is still one line of boilerplate, and it's just messy to create a class from the 3 defining features of `(name, bases, attributes)` only to pull those 3 bits back out from the resulting class, throw the class away, and make a new class from those same 3 bits!
When you do this in Python 2.x:
class A(object):
__metaclass__ = MyMeta
def __init__(self):
pass
You'd get roughly the same result if you'd written this:
attrs = {}
attrs['__metaclass__'] = MyMeta
def __init__(self):
pass
attrs['__init__'] = __init__
A = attrs.get('__metaclass__', type)('A', (object,), attrs)
In reality calculating the metaclass is more complicated, as there actually has to be a search through all the bases to determine whether there's a metaclass conflict, and if one of the bases doesn't have `type` as its metaclass and `attrs` doesn't contain `__metaclass__` then the default metaclass is the ancestor's metaclass rather than `type`. This is one situation where I expect your decorator "solution" will differ from using `__metaclass__` directly. I'm not sure exactly what would happen if you used your decorator in a situation where using `__metaclass__` would give you a metaclass conflict error, but I wouldn't expect it to be pleasant.
Also, if there are any other metaclasses involved, your method would result in them running first (possibly modifying what the name, bases, and attributes are!) and then pulling those out of the class and using it to create a new class. This could potentially be quite different than what you'd get using `__metaclass__`.
As for the `__dict__` not giving you a real dictionary, that's just an implementation detail; I would guess for performance reasons. I doubt there is any spec that says the `__dict__` of a (non-class) instance has to be the same type as the `__dict__` of a class (which is also an instance btw; just an instance of a metaclass). The `__dict__` attribute of a class is a "dictproxy", which allows you to look up attribute keys as if it were a `dict` but still isn't a `dict`. `type` is picky about the type of its third argument; it wants a real dict, not just a "dict-like" object (shame on it for spoiling duck-typing). It's not a 2.x vs 3.x thing; Python 3 behaves the same way, although it gives you a nicer string representation of the `dictproxy`. Python 2.4 (which is the oldest 2.x I have readily available) also has `dictproxy` objects for class `__dict__` objects.
Problem
Following this answer it seems that a class' metaclass may be changed after the class has been defined by using the following*: ``` class MyMetaClass(type): # Metaclass magic... class A(object): pass A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) ``` Defining a function ``` def metaclass_wrapper(cls): return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) ``` allows me to apply a decorator to a class definition like so, ``` @metaclass_wrapper class B(object): pass ``` It seems that the metaclass magic is applied to `B`, however `B` has no `__metaclass__` attribute. Is the above method a sensible way to apply metaclasses to class definitions, even though I am definiting and re-definiting a class, or would I be better off simply writing ``` class B(object): __metaclass__ = MyMetaClass pass ``` I presume there are some differences between the two methods. *Note, the original answer in the linked question, `MyMetaClass(A.__name__, A.__bases__, A.__dict__)`, returns a `TypeError`: TypeError: type() argument 3 must be a dict, not dict_proxy It seems that the `__dict__` attribute of `A` (the class definition) has a type `dict_proxy`, whereas the type of the `__dict__` attribute of an instance of `A` has a type `dict`. Why is this? Is this a Python 2.x vs. 3.x difference?