4

I'm trying to implement a dict-like object which can be accessed/modified with __getattr__ and __setattr__ for ease of use for my users. The class also implements some other simple functionality.

Using this answer as a template, my implementation is currently as follows:

from collections import MutableMapping

class Dictish (MutableMapping):
    """
    A dict-like mapping object. vals are always coerced to str.
    Should provide __getattr__ and __setattr__ as aliases for
    __getitem__ and __setitem__.
    """
    def __init__ ( self, *args, **kwargs ):
        self.store = dict()
        self.update(dict(*args,**kwargs))

    def __getitem__ ( self, key : str ) -> str:
        return self.store[key]

    def __setitem__ ( self, key : str, val : str ) -> None:
        self.store[key] = str(val)

    def __delitem__ ( self, key : str ) -> None:
        del self.store[key]

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

    def __len__ ( self ) -> int:
        return len(self.store)

    def __repr__ ( self ) -> str:
        return repr(self.store)

    # works fine by itself, but goes into infinite recursion
    # when __setattr__ is defined
    def __getattr__ ( self, attr : str ) -> str:
        return self.__getitem__(attr)

#    def __setattr__ ( self, attr : str, val : str ) -> None:
#        self.__setitem__(attr,val)

1 Answer 1

4

As I was writing and formalizing the question, I found the answer (happens to me a lot). Maybe this can help someone else.

The solution for me was the following:

def __getattr__ ( self, attr : str ) -> str:
    return self.__getitem__(attr)

def __setattr__ ( self, attr : str, val : str ) -> None:
    if attr == 'store':
        super().__setattr__(attr,val)
    else:
        self.__setitem__(attr,val)

The key is that the store attribute must be separated out and called from the base class to avoid recursion. Pretty simple but was easy for me to miss!

UPDATE:

I added functionality for adding attributes that you do not want to keep in store (ie. the usual meaning of attributes). I also implemented store as an OrderedDict, but this is just for my use-case. Obviously the set_inst_attr exception is temporary/a placeholder.

from collections import MutableMapping, OrderedDict

class ODictish (MutableMapping):
    """
    An OrderedDict-like mapping object.
    Provides __getattr__ and __setattr__ as aliases for __getitem__
    and __setitem__.
    Attributes which you do not want to keep in 'store' can be set with
    self.set_inst_attr.
    """
    def __init__ ( self , od=None):
        if od is None: od = OrderedDict()
        super().__setattr__('store', OrderedDict(od))

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

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

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

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

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

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

    def __getattr__ ( self, attr ):
        if attr in vars(self):
            return vars(self)[attr]
        return self.__getitem__(attr)

    def __setattr__ ( self, attr, val ):
        if attr in vars(self):
            self.set_inst_attr(attr,val)
        else:
            self.__setitem__(attr,val)

    def set_inst_attr ( self, attr, val ):
        if attr == 'store':
            raise Exception("Don't do that.")
        super().__setattr__(attr,val)

    def move_to_end ( self, key, last=True ):
        self.store.move_to_end(key,last)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.