代码之家  ›  专栏  ›  技术社区  ›  Björn Pollex

为什么更新元组中的集合会导致错误?

  •  3
  • Björn Pollex  · 技术社区  · 14 年前

    我刚刚在Python2.6中尝试了以下内容:

    >>> foo = (set(),)
    >>> foo[0] |= set(range(5))
    TypeError: 'tuple' object does not support item assignment
    >>> foo
    (set([0, 1, 2, 3, 4]),)
    >>> foo[0].update(set(range(10)))
    >>> foo
    (set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),)
    

    我有几个问题:

    • 为什么会这样 foo[0] |= set(range(5))
    • 为什么会这样 foo[0].update(set(range(10))) 工作没有问题?它是否应该有与第一个语句相同的结果?

    编辑 |= 会创造一个新的 set 对象并将其分配给元组。那是错误的。看这个:

    >>> foo = set()
    >>> bar = foo
    >>> foo is bar
    True
    >>> foo |= set(range(5))
    >>> foo
    set([0, 1, 2, 3, 4])
    >>> bar
    set([0, 1, 2, 3, 4])
    >>> foo is bar
    True
    

    这意味着没有创建新对象,但是修改了现有对象。这应该适用于元组。请注意,虽然我的第一个代码 TypeError ,元组中的集合仍会更新。这就是我感兴趣的效果。为什么是 类型错误

    8 回复  |  直到 14 年前
        1
  •  11
  •   jchl    14 年前
    >>> def f():
    ...   x = (set(),)
    ...   y = set([0])
    ...   x[0] |= y
    ...   return   
    ... 
    >>> import dis
    >>> dis.dis(f)
      2           0 LOAD_GLOBAL              0 (set)
                  3 CALL_FUNCTION            0
                  6 BUILD_TUPLE              1
                  9 STORE_FAST               0 (x)
    
      3          12 LOAD_GLOBAL              0 (set)
                 15 LOAD_CONST               1 (0)
                 18 BUILD_LIST               1
                 21 CALL_FUNCTION            1
                 24 STORE_FAST               1 (y)
    
      4          27 LOAD_FAST                0 (x)
                 30 LOAD_CONST               1 (0)
                 33 DUP_TOPX                 2
                 36 BINARY_SUBSCR       
                 37 LOAD_FAST                1 (y)
                 40 INPLACE_OR          
                 41 ROT_THREE           
                 42 STORE_SUBSCR        
    
      5          43 LOAD_CONST               0 (None)
                 46 RETURN_VALUE        
    

    x[0] |= y 通过调用 x[0].__ior__(y) 然后将返回值赋给 x[0] .

    set |= 通过拥有 set.__ior__ 返回 self . 但是,分配给 x[0] 仍然发生。事实上,它分配的值与已经存在的值是不相关的;其失败的原因与以下原因相同:

    x = (set(),)
    x[0] = x[0]
    

    失败。

        2
  •  2
  •   Manuel Salvadores    14 年前

    在你的例子中 foo Tuples 在python中是不可变的,这意味着您不能更改任何元组元素的引用- foo[0]

    >>> x = ('foo','bar')
    >>> x[0]='foo2'
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: 'tuple' object does not support item assignment
    >>> 
    

    你需要一个 list 相反

    >>> foo = [set(),None]
    >>> foo
    [set([]), None]
    >>> foo[0] |= set(range(5))
    >>> foo
    [set([0, 1, 2, 3, 4]), None]
    >>> 
    
        3
  •  1
  •   S.Lott    14 年前
    foo[0] |= set(range(5)) 
    

    foo[0] = foo[0] | set(range(5))
    

    而且不能将新元素赋给旧元组,因为它们是不可变的。例如,您不能这样做:

    x = (0, 1, 2)
    x[0] = 3
    

    运行update时,不更改元组中的引用,只更改引用后面的对象。你也可以这样做:

    x = set()
    y = (x,)
    x.update(set(range(5))
    

    正如你看到的,你没有改变元组,但是 x (和 y[0] )将被更改。

    x |= y
    

    x.update(y)
    

    不一样,因为 update 工程到位 x |= y (x | y) 并以名字储存

        4
  •  0
  •   carl    14 年前

    foo[0] ,您正试图更改元组存储的值(对集合的引用)。当你使用 update() 函数,则不更改引用,而是更改实际集。因为引用是相同的,所以这是允许的。

        5
  •  0
  •   Shwetanka    14 年前

    元组是不可变的,因此不能为其重新赋值。但是如果一个元组包含一个可变类型,比如list或set,那么u可以更新它们。 现在,在您使用“|=”的情况下,实际上首先更新集合(它是元组中的一个值),然后将它赋给导致异常的元组。 更新集合后引发异常。

    http://docs.python.org/reference/datamodel.html

        6
  •  0
  •   S.Lott    14 年前

    “为什么是TypeError,而操作显然是成功的?”。

    因为有多重副作用。尽量避免这样。

    这就是

    foo[0]|=set(range(5))更新集合并引发异常

    对的。首先进行突变。

    然后尝试元组突变,但失败了。

    foo[0].update(set(range(10)))可以正常工作吗?

    对的。这一套是变异的。

    不,第一个语句涉及显式赋值——更改元组——这是禁止的。

    第二个语句更新一个不可变元组的成员,这是一个不被禁止的操作,但在推信封时是可疑的。

    但是 法家学者认为,他们不应该是一样的吗?还是类似的?不。

    禁止更新元组对象(通过赋值)。

    不禁止更新现有元组对象的成员(通过mutator函数)。

        7
  •  0
  •   ddaa    14 年前
    1. a |= b 相当于 a = operator.ior(a, b)
    2. s[i] |= b 相当于 s[i] = operator.ior(s[i], b) .
    3. 合同禁止在元组上分配项。
    4. set.__ior__ 方法调用 set.update

    这就解释了你所观察到的行为。

    元组项应为 frozenset 而不是 set

        8
  •  0
  •   Matt Joiner    14 年前

    最好的解释方法是用“代数”来表示:

    foo[0] |= set(range(5))
    foo[0] = set.__ior__(foo[0], set(range(5)))
    tuple.__setitem__(foo, 0, set.__ior__(foo[0], set(range(5))))
    
    foo[0].update(set(range(5)))
    set.__ior__(foo[0], set(range(5)))
    

    update 形式不一样,它会改变 foo[0] 到位。 __or__ 从左操作数和右操作数的元素生成一个新集。然后将其分配回 foo .

    请注意,为了简单起见,对问题没有帮助的扩展不会被扩展(例如 foo[0] -> tuple.__getitem__(foo, 0)

    TypeError 扔进去了 tuple.__setitem__ . tuple 不允许替换其项引用。这个 形状不接触 以任何方式(即它不调用 ).