Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic mapping type

Is there a polymorphic dict-like type in Python? Here is what I mean by polymorphic: consider a basic class hierarchy with an Animal base class and a some derived classes, Cat, Snake, etc. and let's have our mystery mapping type mystery_dict

mapping = mystery_dict({
    Animal : 'foo',
    Cat    : 'bar',
    Snake  : 'baz',
    Python : 'eggs',
    Boa    : 'spam'
})

Now, I want to have the following lines to be true:

mapping[Animal]   == 'foo'
mapping[Cat]      == 'bar'
mapping[Dog]      == 'foo' # No Dog in mapping, take the base class Animal
mapping[Snake]    == 'baz'
mapping[Boa]      == 'spam'
mapping[Anaconda] == 'baz' # No Anaconda in mapping, take the base class Snake

I know that I could use a bunch of isinstance or an "overload set" with functools.singledispatch since Python 3.4 but in some cases, a polymorphic dictionary type would be handy to reduce boilerplate. Does such a type exist in the wild or do I have to create one? Of course, if you have an even better alternative, I would be glad to hear about it.

Note: just in case the question gets asked, I have rather simple needs so it doesn't have to handle multiple inheritance.

like image 269
Morwenn Avatar asked Nov 15 '25 09:11

Morwenn


1 Answers

This is trivial enough to write yourself:

from collections.abc import MutableMapping


class PolymorphicDict(MutableMapping):
    def __init__(self, *args, **kwargs):
        self._mapping = dict(*args, **kwargs)

    def __getitem__(self, key):
        for cls in key.__mro__:
            if cls in self._mapping:
                return self._mapping[cls]
        raise KeyError(key)

    def __delitem__(self, key):
        del self._mapping[key]

    def __setitem__(self, key, value):
        self._mapping[key] = value

    def __iter__(self):
        return iter(self._mapping)

    def __len__(self):
        return len(self._mapping)

This uses the class.__mro__ attribute to list the class hierarchy of the current object, in method lookup order (Method Resolution Order). This sequence includes the current class and lists all classes all the way up to object.

Demo:

>>> class Animal: pass
... 
>>> class Cat(Animal): pass
... 
>>> class Dog(Animal): pass
... 
>>> class Snake(Animal): pass
... 
>>> class Python(Snake): pass
... 
>>> class Boa(Snake): pass
... 
>>> class Anaconda(Snake): pass
... 
>>> Anaconda.__mro__
(<class '__main__.Anaconda'>, <class '__main__.Snake'>, <class '__main__.Animal'>, <class 'object'>)
>>> mapping = PolymorphicDict({
...     Animal : 'foo',
...     Cat    : 'bar',
...     Snake  : 'baz',
...     Python : 'eggs',
...     Boa    : 'spam'
... })
>>> mapping[Animal]
'foo'
>>> mapping[Cat]
'bar'
>>> mapping[Dog]
'foo'
>>> mapping[Snake]
'baz'
>>> mapping[Boa]
'spam'
>>> mapping[Anaconda]
'baz'

You probably want to look at zope.component however; this lets you do exactly this with added interfaces. It has optimised the lookup mapping to a very high degree to scale the principle to far larger registries of object mappings.

like image 81
Martijn Pieters Avatar answered Nov 18 '25 00:11

Martijn Pieters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!