Hacker News new | ask | show | jobs
by dsj36 4548 days 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!

1 comments

This "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-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