python字典的键可变吗_关于python:不可变字典,仅用作另一个字典的键

我需要实现一个hashable dict,这样我就可以使用一个字典作为另一个字典的键。

几个月前我使用了这个实现:python hashable dicts

然而,我收到一位同事的通知,他说:"这并不是一成不变的,因此它是不安全的。"你可以用它,但它确实让我觉得自己像一只悲伤的熊猫。

所以我开始四处寻找,创造一个不变的。我不需要把"钥匙口述"和另一个"钥匙口述"进行比较。它的唯一用途是作为另一本字典的键。

我提出了以下几点:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35class HashableDict(dict):

"""Hashable dict that can be used as a key in other dictionaries"""

def __new__(self, *args, **kwargs):

# create a new local dict, that will be used by the HashableDictBase closure class

immutableDict = dict(*args, **kwargs)

class HashableDictBase(object):

"""Hashable dict that can be used as a key in other dictionaries. This is now immutable"""

def __key(self):

"""Return a tuple of the current keys"""

return tuple((k, immutableDict[k]) for k in sorted(immutableDict))

def __hash__(self):

"""Return a hash of __key"""

return hash(self.__key())

def __eq__(self, other):

"""Compare two __keys"""

return self.__key() == other.__key() # pylint: disable-msg=W0212

def __repr__(self):

"""@see: dict.__repr__"""

return immutableDict.__repr__()

def __str__(self):

"""@see: dict.__str__"""

return immutableDict.__str__()

def __setattr__(self, *args):

raise TypeError("can't modify immutable instance")

__delattr__ = __setattr__

return HashableDictBase()

我使用以下方法测试功能:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17d = {"a" : 1}

a = HashableDict(d)

b = HashableDict({"b" : 2})

print a

d["b"] = 2

print a

c = HashableDict({"a" : 1})

test = {a :"value with a dict as key (key a)",

b :"value with a dict as key (key b)"}

print test[a]

print test[b]

print test[c]

它给出:

{'a': 1}

{'a': 1}

value with a dict as key (key a)

value with a dict as key (key b)

value with a dict as key (key a)

作为输出

这是"最好的"不变的字典,我可以使用它来满足我的要求吗?如果不是,那么什么是更好的解决方案呢?

更好的方法是tuple(sorted(immutableDict.items()))(或iteritems()pre 3.x)。另外,正如一个注释,我会将FrozenDict作为一个名称,给出默认存在于python中的frozenset类,只是为了命名的一致性——这并不重要。

您的同事可能错过了"同意成人语言"的要点,即纯Python代码中的任何内容都不是真正私有的(在强制执行的意义上)。您的代码所做的非常接近于创建不可变对象的预期方式。考虑一下lib/sets.py中不变的代码,它是由guido van rossum、alex martelli、greg wilson和我自己编写的。标准库代码中的核心开发人员代码是否会让您的同事"感觉像一只悲伤的熊猫"?

如果您只将它用作另一个dict的密钥,则可以使用frozenset(mutabledict.items())。如果需要访问底层映射,那么可以将其用作dict的参数。

1

2

3

4mutabledict = dict(zip('abc', range(3)))

immutable = frozenset(mutabledict.items())

read_frozen = dict(immutable)

read_frozen['a'] # => 1

注意,您也可以将它与派生自dict的类结合起来,并使用frozenset作为散列的源,同时禁用__setitem__,如另一个答案中所建议的那样。(@raymondhettinger的代码答案。

我喜欢这样——dict本质上是无秩序的,所以对它进行排序,然后使其成为一个元组,似乎是一种通过强制排序来确保平等的黑客方式——如果您存储的内容有奇怪的顺序,这可能会中断。这样做不行。这样更简单,更干净,我会说最好。

映射抽象基类使得这很容易实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23import collections

class ImmutableDict(collections.Mapping):

def __init__(self, somedict):

self._dict = dict(somedict) # make a copy

self._hash = None

def __getitem__(self, key):

return self._dict[key]

def __len__(self):

return len(self._dict)

def __iter__(self):

return iter(self._dict)

def __hash__(self):

if self._hash is None:

self._hash = hash(frozenset(self._dict.items()))

return self._hash

def __eq__(self, other):

return self._dict == other._dict

我喜欢你的回答,但它仍然不是一成不变的。我们仍然可以到达ImmutableDict({"a" : 1}).dict变量并更改它。是的,你可以让它被__dict隐藏,但是你仍然可以通过使用ImmutableDict({"a" : 1})._ImmutableDict__dict找到它。因此,它不是"真的"不变的;-)

您还缺少__eq__方法。它也在使用那个。当您随后更改.dict时,self.hash将不会被更新,看起来它仍将使用它,但它不会使用它来比较它们的键。它还使用__eq__。当我重写它并比较__hash__方法时,它确实起作用了吗?

我已经实现了@raymondhettinger的解决方案,并对其进行了打包,使其能够用于pip install。查看我的答案了解更多详细信息。

如果要对字典集合进行排序,还可能需要实现__cmp__。

为了使不可变字典安全,它所需要做的就是永远不要更改其哈希值。为什么不按如下方式禁用__setitem__?

1

2

3

4

5

6

7

8

9

10

11class ImmutableDict(dict):

def __setitem__(self, key, value):

raise Exception("Can't touch this")

def __hash__(self):

return hash(tuple(sorted(self.items())))

a = ImmutableDict({'a':1})

b = {a:1}

print b

print b[a]

a['a'] = 0

脚本的输出为:

1

2

3

4

5

6

7

8{{'a': 1}: 1}

1

Traceback (most recent call last):

File"ex.py", line 11, in

a['a'] = 0

File"ex.py", line 3, in __setitem__

raise Exception("Can't touch this")

Exception: Can't touch this

仍然不是100%不变的,因为object.__setattr__可以绕过这一点。埃多克斯1〔2〕

我知道这已经得到了回答,但是types.mappingProxyType是Python3.3的一个类似的实现。关于最初的安全问题,在PEP416中有一个讨论——添加一个frozendict内置类型,说明为什么拒绝frozendict的想法。

这里有一个指向pip install的链接,可以实现@raymondhettinger的答案:https://github.com/pcattori/icicle

只需pip install icicle,您就可以使用from icicle import FrozenDict!

更新:icicle已被否决,支持maps:https://github.com/pcattori/maps(documentation,pypi)。

看来我迟到了。不确定是否有人想出了主意。但这是我的看法。该dict是不可变的和可散列的。我用一个自定义的"readonly"函数重写了所有的方法(magic等),使其不可变,从而引发了异常。这是在对象实例化时完成的。为了解决无法应用值的问题,我在"新"下设置了"hash"。然后我重写"uuu hash"函数。就这样!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15class ImmutableDict(dict):

_HASH = None

def __new__(cls, *args, **kwargs):

ImmutableDict._HASH = hash(frozenset(args[0].items()))

return super(ImmutableDict, cls).__new__(cls, args)

def __hash__(self):

return self._HASH

def _readonly(self, *args, **kwards):

raise TypeError("Cannot modify Immutable Instance")

__delattr__ = __setattr__ = __setitem__ = pop = update = setdefault = clear = popitem = _readonly

测试:

immutabled1 = ImmutableDict({"This":"That","Cheese":"Blarg"})

dict1 = {immutabled1:"Yay"}

dict1[immutabled1]

"Yay"

dict1

{{'Cheese': 'Blarg', 'This': 'That'}: 'Yay'}

用types.MappingProxyType包住self._dict,从而改变了雷蒙德·赫廷格的回答。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28class ImmutableDict(collections.Mapping):

"""

Copies a dict and proxies it via types.MappingProxyType to make it immutable.

"""

def __init__(self, somedict):

dictcopy = dict(somedict) # make a copy

self._dict = MappingProxyType(dictcopy) # lock it

self._hash = None

def __getitem__(self, key):

return self._dict[key]

def __len__(self):

return len(self._dict)

def __iter__(self):

return iter(self._dict)

def __hash__(self):

if self._hash is None:

self._hash = hash(frozenset(self._dict.items()))

return self._hash

def __eq__(self, other):

return self._dict == other._dict

def __repr__(self):

return str(self._dict)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值