Python: 创建后冻结字典键

12

是否可以在创建后“冻结”Python字典,以使无法向其添加新键? 仅可以更改现有键的值。

如果不行,如何知道何时更改现有键值对,以及何时添加新键值对?


所以你想要冻结字典中的键,而不是整个字典,对吗? - enrico.bacis
2
@jonrsharpe -- 我不确定我是否同意关闭这个问题。OP说“冻结”,但实际上并不是真的想要冻结,而只是想要指定可以操作的键,并禁止其他任何键。. . - mgilson
是的。但是了解两种语言会更好。 - danihodovic
1
相关:https://dev59.com/wHE85IYBdhLWcg3wl0nF - jonrsharpe
1
我认为这不是完全重复的,因为您只想冻结键。顺便说一下,在标准库中没有这样的东西。为了知道一个键是否在字典中,请使用@iwin的建议:print 'already in' if key in dict else 'not here' - enrico.bacis
显示剩余4条评论
2个回答

13
也许像这样:
class FreezableDict (dict):
    __frozen = False

    def freeze (self):
        self.__frozen = True

    def __setitem__ (self, key, value):
        if self.__frozen and key not in self:
            raise ValueError('Dictionary is frozen')
        super().__setitem__(key, value)

>>> x = FreezableDict({'foo': 'bar', 'baz': 'bla'})
>>> x
{'baz': 'bla', 'foo': 'bar'}
>>> x['asdf'] = 'fdsa'
>>> x
{'asdf': 'fdsa', 'baz': 'bla', 'foo': 'bar'}
>>> x.freeze()
>>> x['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    x['hello'] = 'world'
  File "<pyshell#13>", line 8, in __setitem__
    raise ValueError('Dictionary is frozen')
ValueError: Dictionary is frozen

请注意,您可能还需要覆盖其他方法,包括 __delitem__updatesetdefaultpoppopitem,因为它们都可以修改字典。
如果你想完全锁定字典,可以使用types.MappingProxyType,它提供了对字典的只读视图。一旦创建了普通字典,就可以创建一个映射代理来使用它,映射代理不具备任何赋值/更新功能。还可以消除对原始字典的任何引用(映射将保留一个),以防止它被用于进一步更新:
>>> x = {'foo': 'bar'}
>>> y = types.MappingProxyType(x)
>>> y
mappingproxy({'foo': 'bar'})
>>> x['baz'] = 'bla'
>>> y
mappingproxy({'baz': 'bla', 'foo': 'bar'})
>>> y['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    y['hello'] = 'world'
TypeError: 'mappingproxy' object does not support item assignment
>>> del x
>>> y
mappingproxy({'baz': 'bla', 'foo': 'bar'})

或者只在一行中,而不需要参考原始字典:

>>> x = types.MappingProxyType({'foo': 'bar', 'baz': 'bla'})
>>> x
mappingproxy({'baz': 'bla', 'foo': 'bar'})
>>> x['hello'] = 'world'
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    x['hello'] = 'world'
TypeError: 'mappingproxy' object does not support item assignment

喜欢使用 types.MappingProxyType - 绝对 +1 - Jon Clements
2.7 版本中是否有 MappingProxyType 的等效类型? - wim
你是在说 dictproxy 吗? - wim
@wim 是的,从它的内部使用来看,这相当于 mappingproxy 的等效物。 - poke
非常有趣,谢谢。我尝试使用巧妙的 type(type('', (), {}).__dict__)({}) 创建一个 dictproxy,但是失败了 ;) - wim
显示剩余4条评论

4

这不是使用"vanilla"字典的可能。你可能需要子类化collections.MutableMapping...

未经测试的代码如下

class FrozenKeyDict(collections.MutableMapping):
    """Mapping which doesn't allow keys to be added/deleted.

    It does allow existing key/value pairs to be modified.
    """
    def __init__(self, *args, **kwargs):
        self._frozen = False
        self._dict = {}
        super(FrozenKeyDict, self).__init__(*args, **kwargs)
        self._frozen = True

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

    def __setitem__(self, key, value):
        if self._frozen and key not in self._dict:
            raise KeyError('must be one of %s' % list(self))
        self._dict[key] = value

    def __delitem__(self, key):
        # modify to suit your needs ...
        raise KeyError('Removing keys not supported')

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

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

@JonClements -- 哎呀!这就是当你不能决定如何命名事物并且没有使用Python3.x作为示例时的情况。;-) - mgilson
@JonClements -- 我对此有些犹豫 -- 人们经常期望list(self._dict)列出items,而不仅仅是键。也许使用list(self)可以强制一点思考? - mgilson
嗯,list(self) 将为空,因为所有数据都在 self._dict 而不是对象的 collections.MutableMapping 部分(选择其中一个)。 - Jon Clements
@JonClements -- __iter__(self) 只是返回 iter(self._dict),所以 list(self._dict)list(self) 应该总是返回相同的结果。对吧?(__iter__MutableMapping 接口的必需部分) 我们甚至可以在上面的成员测试中使用这个想法 -- if self._frozen and key not in self,但我认为 dict.__contains__ 可能比 MutableMapping.__contains__ 更有效率,因为后者严重依赖于异常处理。 - mgilson
但是,也许这意味着list(self)需要太多的思考(如果它让你感到困惑...)叹气 - mgilson
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接