Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python inherit magic methods based off __init__ value

Tags:

Let's imagine I have a single class X. The purpose of X is to wrap a list or dict and provide event-listening capabilities. All works well.

class X(object):
    def __init__(self, obj)
        self._obj = obj

    def __getattr__(self, name):
        # do stuff with self._obj

    def __getitem__(self, key):
        return self._obj[key]

    def __setitem__(self, key, val):
        self._obj[key] = val

    # rest of functionality ...

So this can be used to wrap a dict like so:

x = X({
    'foo' : False
})

x.listen('foo', callback)

X['foo'] = True         # triggers event
X.update({
    'foo' : False       # triggers event
})

Or a list:

x = X([1,2])

x.listen(callback)

X.append(1)        # triggers event
X[0] = 10          # triggers event

Great. Almost to what I wanted to accomplish ...

Now the current issue is that, because X is for both list and dict objects, it can't inherit from either. This means I don't have the magic class functions, such as __contains__.

Which leads code like this

d = X({
        'foo' : True    
    })

    if 'foo' in d:
        print 'yahoo!'

Throwing a KeyError.

How can I work around this without defining every magic method I need inside of X. If I did it this way, for each of those definitions I would have to write two return values based off whether self._obj is a list or dict.

I thought I could do this with metaclasses at first but that doesn't seem to be a solution, since I need access to the values being passed to check whether it's a dict or list.

like image 248
X33 Avatar asked Sep 22 '17 15:09

X33


1 Answers

An easy way would be to use a proxy class, for example wrapt.ObjectProxy. It will behave exactly like the "proxied" class except for the overridden methods. However instead of self._obj you can simply use self.__wrapped__ to access the "unproxied" object.

from wrapt import ObjectProxy

class Wrapper(ObjectProxy):
    def __getattr__(self, name):
        print('getattr')
        return getattr(self.__wrapped__, name)

    def __getitem__(self, key):
        print('getitem')
        return self.__wrapped__[key]

    def __setitem__(self, key, val):
        print('setitem')
        self.__wrapped__[key] = val

    def __repr__(self):
        return repr(self.__wrapped__)

This behaves like a dict if you wrap a dict:

>>> d = Wrapper({'foo': 10})
>>> d['foo']
getitem
10
>>> 'foo' in d   # "inherits" __contains__
True

and like a list, if a list is wrapped:

>>> d = Wrapper([1,2,3])
>>> d[0]
getitem
1
>>> for i in d:   # "inherits" __iter__
...     print(i)
1
2
3
like image 158
MSeifert Avatar answered Oct 11 '22 14:10

MSeifert