Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to inverse inheritance using a Python meta class?

Tags:

Out of curiosity, I'm interested whether it's possible to write a meta class that causes methods of parent classes to have preference over methods of sub classes. I'd like to play around with it for a while. It would not be possible to override methods anymore. The base class would have to call the sub method explicitly, for example using a reference to a base instance.

class Base(metaclass=InvertedInheritance):

    def apply(self, param):
        print('Validate parameter')
        result = self.subclass.apply(param)
        print('Validate result')
        return result


class Child(Base):

    def apply(self, param):
        print('Compute')
        result = 42 * param
        return result


child = Child()
child.apply(2)

With the output:

Validate parameter
Compute
Validate result

like image 316
danijar Avatar asked Jul 15 '16 21:07

danijar


People also ask

What is the use of meta class in Python?

A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.

Are metaclasses inherited Python?

Every object and class in Python is either an instance of a class or an instance of a metaclass. Every class inherits from the built-in basic base class object , and every class is an instance of the metaclass type .

Is inheritance bidirectional in Python?

Bidirectional Tree Traversal in Python Inheritance Python's inheritance implementation has another distinguishing characteristic; in a field of imperative languages that allow influence to flow only down the chain of inheritance, Python allows subclasses to send notifications back up.


1 Answers

If you only care about making lookups on instances go in reverse order (not classes), you don't even need a metaclass. You can just override __getattribute__:

class ReverseLookup:
    def __getattribute__(self, attr):
        if attr.startswith('__'):
            return super().__getattribute__(attr)
        cls = self.__class__
        if attr in self.__dict__:
            return self.__dict__[attr]
        # Using [-3::-1] skips topmost two base classes, which will be ReverseLookup and object
        for base in cls.__mro__[-3::-1]:
            if attr in base.__dict__:
                value = base.__dict__[attr]
                # handle descriptors
                if hasattr(value, '__get__'):
                    return value.__get__(self, cls)
                else:
                    return value
        raise AttributeError("Attribute {} not found".format(attr))

class Base(ReverseLookup):
    def apply(self, param):
        print('Validate parameter')
        result = self.__class__.apply(self, param)
        print('Validate result')
        return result

class Child(Base):
    def apply(self, param):
        print('Compute')
        result = 42 * param
        return result

>>> Child().apply(2)
Validate parameter
Compute
Validate result
84

This mechanism is relatively simple because lookups on the class aren't in reverse:

>>> Child.apply
<function Child.apply at 0x0000000002E06048>

This makes it easy to get a "normal" lookup just by doing it on a class instead of an instance. However, it could result in confusion in other cases, like if a base class method tries to access a different method on the subclass, but that method actually doesn't exist on that subclass; in this case lookup will proceed in the normal direction and possibly find the method on a higher class. In other words, when doing this you have be sure that you don't look any methods up on a class unless you're sure they're defined on that specific class.

There may well be other corner cases where this approach doesn't work. In particular you can see that I jury-rigged descriptor handling; I wouldn't be surprised if it does something weird for descriptors with a __set__, or for more complicated descriptors that make more intense use of the class/object parameters passed to __get__. Also, this implementation falls back on the default behavior for any attributes beginning with two underscores; changing this would require careful thought about how it's going to work with magic methods like __init__ and __repr__.

like image 195
BrenBarn Avatar answered Sep 28 '22 03:09

BrenBarn