可切换的点访问Python dicts?

时间:2013-08-27 10:16:38

标签: python dictionary getattr setattr

我还没有看过字典点访问的可切换版本。

我的首次尝试不起作用:

class DottableDict (dict):

    def allowDotting (self, state=True):
        if state:
            self.__setattr__ = dict.__setitem__
            self.__getattr__ = dict.__getitem__
        else:
            del self.__setattr__
            del self.__getattr__

>>> import dot
>>> d = dot.DottableDict()
>>> d.allowDotting()
>>> d.foo = 'bar'
>>> d
{}
>>> d.foo
'bar'
>>> d.__dict__
{'__setattr__': <slot wrapper '__setitem__' of 'dict' objects>, 'foo': 'bar',
'__getattr__': <method '__getitem__' of 'dict' objects>}
>>> d.allowDotting(False)
>>> d.__dict__
{'foo': 'bar'}

我认为签名在setattr和setitem之间不匹配。

我的第二次传球似乎也应该有效,但以同样的方式失败:

class DottableDict (dict):

    def dotGet (self, attr):
        return dict.__getitem__(self, attr)

    def dotSet (self, attr, value):
        return dict.__setitem__(self, attr, value)

    def allowDotting (self, state=True):
        if state:
            self.__getattr__ = self.dotGet
            self.__setattr__ = self.dotSet
        else:
            del self.__setattr__
            del self.__getattr__

1 个答案:

答案 0 :(得分:7)

如果设置self.__dict__ = self,则dict将自动变为“dottable”。您可以通过设置self.__dict__ = {}来关闭“点能力”。但是,键值对仍然可以通过索引来访问。这个想法主要来自katrielalex's bio page

class DottableDict(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.__dict__ = self
    def allowDotting(self, state=True):
        if state:
            self.__dict__ = self
        else:
            self.__dict__ = dict()

d = DottableDict()
d.allowDotting()
d.foo = 'bar'

print(d['foo'])
# bar
print(d.foo)
# bar

d.allowDotting(state=False)
print(d['foo'])
# bar
print(d.foo)
# AttributeError: 'DottableDict' object has no attribute 'foo'         

记住Python的禅,但是:

  

应该有一个 - ,最好只有一个 - 显而易见的方法。

通过限制自己使用dict访问的标准语法,可以提高自己和他人的可读性/可维护性。


工作原理:

当您键入d.foo时,Python会在多个位置查找'foo',其中一个位于d.__dict__。 (它还会查看d.__class__.__dict__以及__dict__中列出的所有基础的所有d.__class__.mro() ...有关属性查找的完整详细信息,请参阅此excellent article by Shalabh Chaturvedi

无论如何,对我们来说重点是d.__dict__中的所有键值对都可以用点表示法访问。

这一事实意味着我们可以通过将d设置为d.__dict__来获得d中键值对的点访问权限!毕竟,d是一个字典,而d.__dict__期望一个类似字母的对象。请注意,这也是内存效率高的。我们不会复制任何键值对,我们只是简单地将d.__dict__指向已经存在的词典。

此外,通过将d.__dict__分配给dict(),我们有效地关闭了对d中键值对的点访问。 (这并不能完全禁用点访问 - 例如d.__class_.__dict__中的键值对,仍然可以通过点表示法访问。谢天谢地,这是真的,或者你无法调用{{1}再次方法!)

现在您可能想知道这是否会删除allowDotting本身中的所有键值对。答案是否定的。

键值对未存储在d属性中。实际上,普通的dict没有__dict__属性。因此设置__dict__只会将字典重置为中性条件。我们本来可以用

d.__dict__ = {}

insteaad

del self.__dict__

太。但是,由于self.__dict__ = dict() DottableDict中被赋予了__dict__属性,因此允许__init__的实例始终具有DottableDict属性似乎更加清晰。


您在评论中注意到:

  

步骤:关闭访问权限,将d.foo设置为“bar”,打开访问权限,d.foo为   从各地走了。

要保留在__dict__关闭时设置的d.foo等属性,您需要存储已设置allowDotting的备用字典。

self.__dict__

按照惯例,以单个下划线开头的属性被理解为私有的实现细节。我通过在dict中引入私钥class DottableDict(dict): def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) self['_attributes'] = dict() self.allowDotting() def allowDotting(self, state=True): if state: self.update(self['_attributes']) self.__dict__ = self else: self.__dict__ = self['_attributes'] d = DottableDict() d.allowDotting(state=False) d.foo = 'bar' d.allowDotting(state=True) print(d.foo) # bar d.allowDotting(state=False) print(d.foo) # bar d.allowDotting(state=True) print(d.foo) # bar 来扩展约定。