9 ms·
I think it was David Beazley who characterized metaclasses as "infecting" an inheritance tree. If `class A` has a metaclass, then all subclasses of `A` will in
by dsj36 13y ago
I think it was David Beazley who characterized metaclasses as "infecting" an inheritance tree. If `class A` has a metaclass, then all subclasses of `A` will inherit the metaclass as well. This behavior is not shared by class decorators, which only affect the decorated class.
So, I think this is a pretty reasonable place to use them -- getting magic behavior from class decorators / metaclasses is bad enough, but getting surprised when losing it upon subclassing is even worse!
- jamesdutc 13y agoThis "infectious behaviour" (though perhaps unfairly characterised) leads to the clearest purpose of metaclasses. Metaclasses (and build_class) is a mechanism by which to enforce a constraint from a base type to a derived type. (Note that, in practice, there is some trickiness around metaclasses on derived classes: http://seriously.dontusethiscode.com/2013/04/18/derived-metaclasses.html http://seriously.dontusethiscode.com/2013/04/18/derived-meta...) It's trivial to enforce a constraint from a derived type to a base type. e.g., # base.py class Base: def spam(self): pass # derived.py class Derived(Base): assert hasattr(Base, 'spam') # or abc, &c. def ham(self): return self.spam But how can we enforce a constraint in the other direction? (e.g., abc.ABCMeta) # base.py from functools import wraps class metaclass(type): def __new__(m, n, b, d): assert 'spam' in d # must implement, not just inherit # can even enforce behaviour via wrapping spam = d['spam'] @wraps(spam) def wrap(*args, **kwargs): print('wrap({}, {})'.format(args, kwargs)) return spam(*args, **kwargs) d['spam'] = wrap return type.__new__(m, n, b, d) class Base(metaclass=metaclass): def spam(self): pass # derived.py class Derived(Base): def spam(self): pass