Skip to content

Instantly share code, notes, and snippets.

@rmorshea
Last active May 26, 2016 02:42
Show Gist options
  • Save rmorshea/7a1b130f0792a5b1babffb37bfc30744 to your computer and use it in GitHub Desktop.
Save rmorshea/7a1b130f0792a5b1babffb37bfc30744 to your computer and use it in GitHub Desktop.
call methods of a parent class during class construction.
import inspect
def metamethod(cls, name):
"""Get a method owned by a metaclass
The method is retrieved from the instantiating metaclass.
Parameters
----------
cls: class
The class whose instantiating metaclass will be searched.
"""
meta = cls.__class__
try:
cm = classmethod(getattr(meta, name))
except:
# attribute error or type error
m = "no metamethod named '%s'"
raise AttributeError(m % name)
return cm.__get__(None, cls)
class MetaSuperClass(type):
"""A class proxy injected by a metasuper object"""
def __new__(mcls, msuper, head):
if isinstance(msuper, metasuper):
return super(MetaSuperClass, mcls).__new__(
MetaSuperClass, msuper.__base__.__name__, (object,),
{'__metasuper__': msuper, '__mropos__': head})
else:
raise TypeError('expected a metasuper instance')
def __init__(cls, *args, **kwargs): pass
def __getattr__(cls, name):
return getattr(cls.__metasuper__.__base__, name)
def __setattr__(cls, name, value):
setattr(cls.__metasuper__.__base__, name, value)
@property
def __class__(cls):
return cls.__metasuper__.__base__.__class__
class metasuper(object):
"""Access super classmethods during class construction"""
def __new__(cls, base, head=None):
if inspect.isclass(base):
if hasattr(base, '__metasuper__') and hasattr(base, '__mropos__'):
return base.__metasuper__.copy(base.__mropos__)
else:
inst = super(metasuper, cls).__new__(cls)
inst.__head__ = head or base
inst.__base__ = base
return inst
else:
m = "expected a class, not: '%s'"
raise ValueError(m % repr(type(cls)))
def __getattr__(self, name):
initial_find = False
for c in self.__head__.mro():
if name in c.__dict__:
v = c.__dict__[name]
if initial_find:
if isinstance(v, classmethod):
return c.__dict__[name].__get__(
None, MetaSuperClass(self, c))
else:
m = "Expected '%s' to be a classmethod"
raise TypeError(m % name)
else:
initial_find = True
continue
else:
cls = self.__class__.__name__
m = "'%s' object has no attribute '%s'"
raise AttributeError(m % (cls, name))
def copy(self, head):
return self.__class__.__new__(
self.__class__, self.__base__,
head)
@rmorshea
Copy link
Author

rmorshea commented May 26, 2016

MetaSuper

Call methods of a parent class during class construction.

The Hack

A class's method that gets called before its metaclass has completed __new__ and __init__ can't use super (see why). However, metasuper is super-like substitute which allows these kinds of methods to be properly overridden and called in subclasses. Essentially this means metasuper along with metamethod are simple hacks that makes it possible to bypass metaclass inheritance during class construction:

metasuper inheritance

from six import with_metaclass

class Meta(type):
    """a metaclass"""

    def __init__(cls, name, bases, classdict):
        print("Beginning '%s' setup" % name)
        cls.setup_class()
        print("Finished '%s' setup\n" % name)

    def setup_class(cls):
        cls.instantiate('Meta')


class A(with_metaclass(Meta, object)):
    """a class"""

    @classmethod
    def instantiate(cls, name):
        print("instantiating through '%s'" % name)

    @classmethod
    def setup_class(cls):
        metamethod(cls, 'setup_class')()
        cls.instantiate('A')


class B(A):

    @classmethod
    def setup_class(cls):
        metasuper(cls).setup_class()
        cls.instantiate('B')

metaclass inheritance

from six import with_metaclass

class Meta(type):
    """a metaclass"""

    def __init__(cls, name, bases, classdict):
        print("Beginning '%s' setup" % name)
        cls.setup_class()
        print("Finished '%s' setup \n" % name)

    def setup_class(cls):
        cls.attribute = 0
        cls.instantiate('Meta')


class AMeta(Meta):

    def setup_class(cls):
        super(AMeta, cls).setup_class()
        cls.instantiate('A')

    def instantiate(cls, name):
        print("instantiating through '%s'" % name)

class A(with_metaclass(AMeta, object)): pass


class BMeta(AMeta):

    def setup_class(cls):
        super(BMeta, cls).setup_class()
        cls.instantiate('B')

class B(with_metaclass(BMeta, object)): pass

output

Beginning 'A' setup
instantiating through 'Meta'
instantiating through 'A'
Finished 'A' setup 

Beginning 'B' setup
instantiating through 'Meta'
instantiating through 'A'
instantiating through 'B'
Finished 'B' setup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment