可切换的点访问Python dicts?

Gar*_*ler 2 python dictionary setattr getattr

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

我在这里的首次尝试不起作用:

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'}
Run Code Online (Sandbox Code Playgroud)

我认为签名在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__
Run Code Online (Sandbox Code Playgroud)

unu*_*tbu 7

如果你设置self.__dict__ = self,那么dict将自动变为"dottable".您可以通过设置关闭"点能力" self.__dict__ = {}.但是,键值对仍然可以通过索引来访问.这个想法主要来自katrielalex的生物页面:

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'         
Run Code Online (Sandbox Code Playgroud)

但请记住Python的Zen:

应该有一个 - 最好只有一个 -明显的方法来做到这一点.

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


这个怎么运作:

当你输入时d.foo,Python 'foo'在许多地方寻找,其中一个在d.__dict__.(它也会查看d.__class__.__dict__,以及__dict__列出的所有基础的所有内容d.__class__.mro()......有关属性查找的完整详细信息,请参阅Shalabh Chaturvedi撰写的这篇优秀文章).

无论如何,对我们来说重点是所有的键值对d.__dict__都可以用点符号来访问.

这一事实意味着我们可以得到点访问键值对d设置d.__dict__d自己!d毕竟,是一个字典,并d.__dict__期望一个类似字母的对象.请注意,这也是内存效率高的.我们不会复制任何键值对,我们只是简单地指向d.__dict__已存在的词典.

此外,通过分配d.__dict__dict(),我们有效点接入关闭在键值对d.(这并不完全禁用点访问 - d.__class_.__dict__例如,键值对仍然可以通过点表示法访问.谢天谢地,这是真的,否则你将无法allowDotting再次调用该方法!)

现在您可能想知道这是否会删除所有键值对d.答案是不.

键值对不存储在__dict__属性中.实际上,普通的dict没有__dict__属性.所以设置d.__dict__ = {}只是将字典重置为中性条件.我们本来可以用的

del self.__dict__
Run Code Online (Sandbox Code Playgroud)

insteaad

self.__dict__ = dict()
Run Code Online (Sandbox Code Playgroud)

太.但是,由于DottableDict赋予了__dict__属性__init__,因此允许实例DottableDict始终具有__dict__属性似乎更清晰.


在评论中你注意到:

步骤:关闭访问权限,将d.foo设置为"bar",打开访问权限,d.foo从各处消失.

要保留已关闭时d.foo设置的属性allowDotting,您需要存储self.__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
Run Code Online (Sandbox Code Playgroud)

通过传统,以单个下划线开头的属性被理解为私有的实现细节.我通过'_attribute'在dict中引入私钥来扩展约定.