【问题标题】:Unwanted behaviour from dict.fromkeys [duplicate]来自 dict.fromkeys 的不良行为 [重复]
【发布时间】:2022-01-25 20:19:12
【问题描述】:

我想使用 dict.fromkeys 初始化一个集合字典(在 Python 2.6 中),但结果结构的行为很奇怪。更具体地说:

>>>> x = {}.fromkeys(range(10), set([]))
>>>> x
{0: set([]), 1: set([]), 2: set([]), 3: set([]), 4: set([]), 5: set([]), 6: set([]), 7: set([]), 8: set([]), 9: set([])}
>>>> x[5].add(3)
>>>> x
{0: set([3]), 1: set([3]), 2: set([3]), 3: set([3]), 4: set([3]), 5: set([3]), 6: set([3]), 7: set([3]), 8: set([3]), 9: set([3])}

我显然不想将 3 添加到所有集合中,只添加对应于 x[5] 的集合。当然,我可以通过初始化 x 而不使用 fromkeys 来避免这个问题,但我想了解我在这里缺少什么。

【问题讨论】:

  • 他们都是同一套。集合、列表、字典和任何其他对象都是引用类型,当您将它们分配给另一个变量时,只会复制引用,而不是实际对象。 fromkeys 必须使用赋值将集合与每个键相关联,但正如您所见,这不会复制集合。除了以不同的方式创建字典之外,我不确定如何解决这个问题。

标签: python


【解决方案1】:

dict.fromkeys 的第二个参数只是一个值。您已经创建了一个字典,其中 same 设置为每个键的值。想必你了解它的工作原理:

>>> a = set()
>>> b = a
>>> b.add(1)
>>> b
set([1])
>>> a
set([1])

您在那里看到了相同的行为;在您的情况下,x[0]x[1]x[2](等)都是访问完全相同的 set 对象的不同方式。

对于字符串表示形式包括其内存地址的对象,这更容易看到,您可以看到它们是相同的:

>>> dict.fromkeys(range(2), object())
{0: <object object at 0x1001da080>,
 1: <object object at 0x1001da080>}

【讨论】:

    【解决方案2】:

    它以这种方式工作的原因是set([]) 创建了一个对象(一个集合对象)。 Fromkeys 然后使用该特定对象来创建其所有字典条目。考虑:

    >>> x
    {0: set([]), 1: set([]), 2: set([]), 3: set([]), 4: set([]), 5: set([]), 
    6: set([]), 7: set([]), 8: set([]), 9: set([])}
    >>> x[0] is x[1]
    True
    

    所有的集合都是一样的!

    【讨论】:

    • 你真的应该比较身份:x[0] is x[1].
    【解决方案3】:

    因为this来自dictobject.c

    while (_PyDict_Next(seq, &pos, &key, &oldvalue, &hash))
    {
                Py_INCREF(key);
                Py_INCREF(value);
                if (insertdict(mp, key, hash, value))
                    return NULL;
    }
    

    value 是你的“set([])”,它只被评估一次,然后他们的结果对象引用计数增加并添加到字典中,它不会在每次添加到字典时评估它。

    【讨论】:

      【解决方案4】:
      
      #To do what you want:
      
      import copy
      s = set([])
      x = {}
      for n in range(0,5):
        x[n] = copy.deepcopy(s)
      x[2].add(3)
      print x
      
      #Printing
      #{0: set([]), 1: set([]), 2: set([3]), 3: set([]), 4: set([])}
      

      【讨论】:

      • 不需要deepcopyx[n] = set() 为每个值创建一个新集合。
      【解决方案5】:

      您可以使用生成器表达式来做到这一点:

      x = dict( (i,set()) for i in range(10) )
      

      在 Python 3 中,您可以使用字典推导:

      x = { i : set() for i in range(10) }
      

      在这两种情况下,表达式set() 都会为每个元素计算,而不是计算一次并复制到每个元素。

      【讨论】:

      • +1 即使在接受的答案解释得很好之后也能提供解决方案。
      • 如果我想初始化列表而不是集合,x = { i : [] for i in range(10) } 会导致 SyntaxError 而 dict( (i,[]) for i in range( 10) ) 没有。
      • 值得指出的是,字典推导在 Python 2.7 及更高版本中工作。
      猜你喜欢
      • 1970-01-01
      • 2015-08-01
      • 1970-01-01
      • 2013-08-08
      • 2018-04-19
      • 1970-01-01
      • 2021-01-30
      • 2015-02-17
      相关资源
      最近更新 更多