Python中集合定义的区别
让我们来看看在Python中定义一个集合的四种方法,这个集合将包含一个可迭代对象l
中的元素,元素分别是l_1, l_2, l_3, ..., l_n
:
方法1:
def defset(l):
x = set()
x.update(l)
return x
方法2:
set(l)
方法3:
{i for i in l}
方法4:
{l_1, l_2, l_3,..., l_n}
前面三种方法的结果是一样的,但第四种方法的结果却不一样,原因是什么呢?
举个例子,如果我这样做:
s1 = defset([1,2,3,11]
s2 = set([1,2,3,11])
s3 = {i for i in [1,2,3,11]}
s4 = {1,2,3,11}
print(s1, s2, s3, s4, sep = "\n")
输出结果是:
{11,1,2,3}
{11,1,2,3}
{11,1,2,3}
{3,1,2,11}
那么,为什么第四种方法得到的集合和其他方法得到的集合不同呢?我知道在集合的上下文中,这四个集合是相等的,但在实现上却有差异,这改变了元素在内部哈希表中的存储顺序,对吧?
这种差异是怎么产生的呢?
编辑
这个问题没有回答我的疑问。我已经看过强大的字典和后续的更强大的字典,了解字典是如何以哈希表的形式存储数据的。我知道集合的工作原理类似,并且根据哈希表中的“顺序”,它们会遍历或显示它们的元素。
我想问的是,为什么通过这些看似相同和等价的方法定义集合,导致数据在内部(在相应的哈希表中)存储的方式不同呢?
1 个回答
因为顺序是不确定的,所以不同的Python版本、不同的电脑,甚至同一台电脑上不同的运行方式,得到的结果可能会不一样。
不过,dis
这个工具可以帮助你了解你所用版本的一些情况。对我来说,它的结果是这样的:
Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> dis.dis('''s1 = defset([1,2,3,11])
... s2 = set([1,2,3,11])
... s3 = {i for i in [1,2,3,11]}
... s4 = {1,2,3,11}
... ''')
0 0 RESUME 0
1 2 PUSH_NULL
4 LOAD_NAME 0 (defset)
6 BUILD_LIST 0
8 LOAD_CONST 0 ((1, 2, 3, 11))
10 LIST_EXTEND 1
12 CALL 1
20 STORE_NAME 1 (s1)
2 22 PUSH_NULL
24 LOAD_NAME 2 (set)
26 BUILD_LIST 0
28 LOAD_CONST 0 ((1, 2, 3, 11))
30 LIST_EXTEND 1
32 CALL 1
40 STORE_NAME 3 (s2)
3 42 LOAD_CONST 0 ((1, 2, 3, 11))
44 GET_ITER
46 LOAD_FAST_AND_CLEAR 0 (i)
48 SWAP 2
50 BUILD_SET 0
52 SWAP 2
>> 54 FOR_ITER 4 (to 66)
58 STORE_FAST 0 (i)
60 LOAD_FAST 0 (i)
62 SET_ADD 2
64 JUMP_BACKWARD 6 (to 54)
>> 66 END_FOR
68 SWAP 2
70 STORE_FAST 0 (i)
72 STORE_NAME 4 (s3)
4 74 BUILD_SET 0
76 LOAD_CONST 1 (frozenset({3, 1, 2, 11}))
78 SET_UPDATE 1
80 STORE_NAME 5 (s4)
82 RETURN_CONST 2 (None)
>> 84 SWAP 2
86 POP_TOP
前面三个方法都是先加载一个编译代码里内置的常量值:(1, 2, 3, 11)
而最后一个方法则是不同的:它加载了一个不同的常量值:frozenset({3, 1, 2, 11})
。
接下来发生的事情对于这四个方法来说都很相似:它们都会创建一个新的集合,并把这些值添加进去——无论是直接添加还是间接添加。
所以,区别在于对于s4
,在编译时,Python创建了一个常量frozenset
。可以推测,当在运行时从frozenset创建集合时,它们会保持相同的顺序(至少在这个版本中是这样的)。
如果你尝试使用无法从常量构造的集合显示来创建一个集合,会发生什么呢?
>>> dis.dis('s = 11; s1 = {1, 2, 3, s}')
0 0 RESUME 0
1 2 LOAD_CONST 0 (11)
4 STORE_NAME 0 (s)
6 LOAD_CONST 1 (1)
8 LOAD_CONST 2 (2)
10 LOAD_CONST 3 (3)
12 LOAD_NAME 0 (s)
14 BUILD_SET 4
16 STORE_NAME 1 (s1)
18 RETURN_CONST 4 (None)
>>> s = 11
>>> {1, 2, 3, s}
{11, 1, 2, 3}
这个集合也是在运行时构造的,即使你使用了{...}
的语法。
总结:可能是编译时构造的集合和运行时构造的集合之间的区别。