使用__getattr__和__setattr__功能实现类似dict的对象

时间:2017-11-02 17:32:15

标签: python dictionary inheritance mapping setattr

我尝试实现类似dict的对象,可以使用__getattr____setattr__进行访问/修改,以方便用户使用。该类还实现了一些其他简单的功能。

使用this answer作为模板,我的实现目前如下:

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 个答案:

答案 0 :(得分:2)

当我写这个问题并将其正式化时,我找到了答案(在我身上发生了很多)。也许这可以帮助别人。

我的解决方案如下:

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属性并从基类调用以避免递归。很简单但很容易让我错过!

更新:

我添加了添加您不想保留在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)