Реализация диктоподобного объекта с функциями __getattr__ и __setattr__

Я пытаюсь реализовать объект dict -like, который можно получить/изменить с помощью __getattr__ и __setattr__ для удобства использования для моих пользователей. Класс также реализует некоторые другие простые функции.

Используя этот ответ в качестве шаблона, моя реализация в настоящее время выглядит следующим образом:

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

Как я писал и оформлял вопрос, я нашел ответ (бывает со мной много). Может быть, это может помочь кому-то другому.

Решение для меня было следующим:

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)

Ключ состоит в том, что атрибут store должен быть отделен и вызван из базового класса, чтобы избежать рекурсии. Довольно просто, но мне было легко пропустить!

UPDATE:

Я добавил функциональность для добавления атрибутов, которые вы не хотите хранить в store (т.е. обычном значении атрибутов). Я также реализовал store как OrderedDict, но это только для моего прецедента. Очевидно, исключение set_inst_attr является временным/заполнителем.

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)