Python中集合定义的区别

0 投票
1 回答
65 浏览
提问于 2025-04-12 22:12

让我们来看看在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 个回答

0

因为顺序是不确定的,所以不同的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}

这个集合也是在运行时构造的,即使你使用了{...}的语法。

总结:可能是编译时构造的集合和运行时构造的集合之间的区别。

撰写回答