I have the following python code:
class FooMeta(type):
def __setattr__(self, name, value):
print name, value
return super(FooMeta, self).__setattr__(name, value)
class Foo(object):
__metaclass__ = FooMeta
FOO = 123
def a(self):
pass
I would have expected __setattr__
of the meta class being called for both FOO
and a
. However, it is not called at all. When I assign something to Foo.whatever
after the class has been defined the method is called.
What's the reason for this behaviour and is there a way to intercept the assignments that happen during the creation of the class? Using attrs
in __new__
won't work since I'd like to check if a method is being redefined.
There are no assignments happening during the creation of the class. Or: they are happening, but not in the context you think they are. All class attributes are collected from class body scope and passed to metaclass'
__new__
, as the last argument:Reason: when the code in the class body executes, there's no class yet. Which means there's no opportunity for metaclass to intercept anything yet.
Class attributes are passed to the metaclass as a single dictionary and my hypothesis is that this is used to update the
__dict__
attribute of the class all at once, e.g. something likecls.__dict__.update(dct)
rather than doingsetattr()
on each item. More to the point, it's all handled in C-land and simply wasn't written to call a custom__setattr__()
.It's easy enough to do whatever you want to the attributes of the class in your metaclass's
__init__()
method, since you're passed the class namespace as adict
, so just do that.A class block is roughly syntactic sugar for building a dictionary, and then invoking a metaclass to build the class object.
This:
Comes out pretty much as if you'd written:
Only without the namespace pollution (and in reality there's also a search through all the bases to determine the metaclass, or whether there's a metaclass conflict, but I'm ignoring that here).
The metaclass'
__setattr__
can control what happens when you try to set an attribute on one of its instances (the class object), but inside the class block you're not doing that, you're inserting into a dictionary object, so thedict
class controls what's going on, not your metaclass. So you're out of luck.Unless you're using Python 3.x! In Python 3.x you can define a
__prepare__
classmethod (or staticmethod) on a metaclass, which controls what object is used to accumulate attributes set within a class block before they're passed to the metaclass constructor. The default__prepare__
simply returns a normal dictionary, but you could build a custom dict-like class that doesn't allow keys to be redefined, and use that to accumulate your attributes:Running this gives me:
Some notes:
__prepare__
has to be aclassmethod
orstaticmethod
, because it's being called before the metaclass' instance (your class) exists.type
still needs its third parameter to be a realdict
, so you have to have a__new__
method that converts theSingleAssignDict
to a normal onedict
, which would probably have avoided (2), but I really dislike doing that because of how the non-basic methods likeupdate
don't respect your overrides of the basic methods like__setitem__
. So I prefer to subclasscollections.MutableMapping
and wrap a dictionary.Okay.__dict__
object is a normal dictionary, because it was set bytype
andtype
is finicky about the kind of dictionary it wants. This means that overwriting class attributes after class creation does not raise an exception. You can overwrite the__dict__
attribute after the superclass call in__new__
if you want to maintain the no-overwriting forced by the class object's dictionary.Sadly this technique is unavailable in Python 2.x (I checked). The
__prepare__
method isn't invoked, which makes sense as in Python 2.x the metaclass is determined by the__metaclass__
magic attribute rather than a special keyword in the classblock; which means the dict object used to accumulate attributes for the class block already exists by the time the metaclass is known.Compare Python 2:
Being roughly equivalent to:
Where the metaclass to invoke is determined from the dictionary, versus Python 3:
Being roughly equivalent to:
Where the dictionary to use is determined from the metaclass.
During the class creation, your namespace is evaluated to a dict and passed as an argument to the metaclass, together with the class name and base classes. Because of that, assigning a class attribute inside the class definition wouldn't work the way you expect. It doesn't create an empty class and assign everything. You also can't have duplicated keys in a dict, so during class creation attributes are already deduplicated. Only by setting an attribute after the class definition you can trigger your custom __setattr__.
Because the namespace is a dict, there's no way for you to check duplicated methods, as suggested by your other question. The only practical way to do that is parsing the source code.