使用`union`将字典添加到 `set()` 中。

14

我刚刚发现了一些有趣的东西,想问一下。

将字典添加到set中,我曾假设整个字典都会被添加进去,但实际上只有键被添加了:

dicty = {"Key1": "Val1", "Key2": "Val2"}
setunion = set()
setunion.union(dicty)
=> set(['Key2', 'Key1'])

当你尝试使用set.add()添加它时,会出现错误:

setadd = set()
setadd.add(dicty)
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unhashable type: 'dict'

显然,这种行为与列表非常不同:

   listy = []
   listy.append(dicty)
   listy
=> [{'Key2': 'Val2', 'Key1': 'Val1'}]

文档中说,集合是无序的可哈希对象集合,这是上述某些问题的提示。

问题

这里发生了什么?集合项必须是可哈希的,所以显然这与为什么我仅使用.union()将键添加到集合有关,但使用.add()时为什么会出现错误?

集合与列表之间行为差异背后是否存在某种可用性原因?

Python(或库)中是否存在一种数据类型,其本质上类似于列表,但仅保留唯一项?


2
所以你想要一个保留顺序但不允许重复的结构? - senshin
3
你为什么认为union是向集合中添加元素的方法?union的意思是“构建一个新集合,并将参数的元素添加到新集合中”。这就像列表的extend方法,但不是原地操作。 - user2357112
@senshin 我甚至没有考虑过排序问题。我更在意的是任何允许添加任何对象但不允许(或“吸收”)重复项的结构。 - NotAnAmbiTurner
@NotAnAmbiTurner:那不是唯一的区别。add 没有真正的创建新集合的版本。union 的可变版本是 union_update - user2357112
@user2357112 噢,明白了。我在学Python,然后在Hackerrank上摸索,这个网站在新概念方面真的很好,但是可能不是最好的避免混淆的网站。 - NotAnAmbiTurner
@user2357112 但是... 集合显然不允许添加“任何”对象。首先,它们只接受可哈希的对象,并且要将字典放入集合中并取出,您需要做一些相当奇怪的事情。 - NotAnAmbiTurner
2个回答

21

不,这在定义上是不可能的。哈希表(例如 dictset)进行查找的方式与数组(例如 list)进行查找的方式根本不同。逻辑错误在于,如果您有一种只保存重复项的数据类型,那么如果您将其中一个元素突变为非唯一值会发生什么?

a, b = [0], [0, 1]
s = SpecialSet(a, b)
a.append(1)  # NOW WHAT?!

如果您想将字典添加到一个集合中,可以添加它的dict.items视图(实际上只是一个元组列表),但您需要先强制转换为元组。

a = {1:2, 3:4}
s = set()
s.add(tuple(a.items()))

那么一旦它离开集合,您就需要将其重新转换为字典才能获取一个字典回来。

for tup in s:
    new_a = dict(tup)

PEP416中提出了一个内置的frozendict类型,但最终被拒绝了。


@user2357112 哦,我明白了。好的,谢谢。让我试着更清楚地表达。 - NotAnAmbiTurner
我想我的想法是,SpecialSet.__init__会复制并存储每个值,首先确保它们是唯一的。 - NotAnAmbiTurner
@AdamSmith 使其与众不同的一件事是,集合只接受可哈希对象。 - NotAnAmbiTurner
假设我有一些结构的字典,只有其中一个键需要是唯一的,那么我该如何组成这样的列表呢?换句话说,我有一些键值对字典,只有一个键是不可变的,其他键都是可变的。我需要创建一个唯一的字典列表,我该怎么做? - Edik Mkoyan
@AdamSmith 我需要生成一个包含URL和一些HTTP头信息的唯一字典列表,像这样。 { "_id": "http://www.example.com/bd/en/?preferredCountry=yes", "status": 200, "headers": { "Server": "Apache-Coyote/1.1", "Access-Control-Allow-Origin": "http://example.com", "Date": "Mon, 21 Aug 2017 08:52:00 GMT", "Connection": "keep-alive", "Vary": "Accept-Encoding" }, "origin": "http://example.com"}我将迭代该列表,在最后将其添加到MongoDB。 - Edik Mkoyan
显示剩余4条评论

6

使用set.union()方法时,需要传入元素,而非对象本身。遍历字典可以获取键值。如果在列表、元组或字符串上使用set.union()方法,则会将这些对象的内容添加到集合中:

>>> s = {42}
>>> s.union('foo')
set([42, 'o', 'f'])

单个字符的字符串 'o''f' 被添加了,而不是字符串 'foo'

不能向集合中添加字典,因为它们是可变的;集合只支持存储可哈希对象,而一个对象成为可哈希对象的要求之一就是它们是不可变的。


2
注意:您可以在可变对象上实现__hash__ - Adam Smith
2
@AdamSmith:是的,这违反了要求。Python让你朝自己的脚开枪。 - Martijn Pieters
我的观点更多与行为有关。对我来说,你可以将一个字典作为字典添加到列表中,但不能添加到集合中,这很奇怪。我最初认为集合的设计本质上是一个唯一项的“列表”,但似乎不仅如此。 - NotAnAmbiTurner
@NotAnAmbiTurner,setdict 更相似,而不是 list。因为 list 是通过索引引用的,所以它不关心其中的内容。set(以及 dict 的键)是通过哈希引用的,这应该与值密不可分。 - Adam Smith

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