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.
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 thedictclass 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 aclassmethodorstaticmethod, because it’s being called before the metaclass’ instance (your class) exists.typestill needs its third parameter to be a realdict, so you have to have a__new__method that converts theSingleAssignDictto a normal onedict, which would probably have avoided (2), but I really dislike doing that because of how the non-basic methods likeupdatedon’t respect your overrides of the basic methods like__setitem__. So I prefer to subclasscollections.MutableMappingand wrap a dictionary.Okay.__dict__object is a normal dictionary, because it was set bytypeandtypeis 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.