【问题标题】:How is this list expanded with the slicing assignment? [duplicate]这个列表是如何通过切片分配扩展的? [复制]
【发布时间】:2018-06-09 21:56:57
【问题描述】:

我遇到了以下代码(有点):

my_list = [1, [2, 3, 4], 5]
my_list[1:2] = my_list[1]

运行这两行后,变量my_list 将是[1, 2, 3, 4, 5]。对于扩展嵌套列表非常有用。

但为什么它实际上做了它所做的事情?

我会假设语句 my_list[1:2] = my_list[1] 会执行以下操作之一:

  • 只需将[2, 3, 4] 放在列表中的第二个位置(它已经在该位置)
  • 给出某种“太多值无法解包”错误,从尝试将三个值(即 2,3,4)放入长度仅为 1 的容器(即my_list[1:2])。 (使用 Numpy 数组而不是列表重复上述操作会导致类似的错误。)

其他问题(例如How assignment works with python list slice)往往不太注意要替换的切片大小与您要替换的项目大小之间的差异。 (更不用说解释它为什么会这样工作了。)

【问题讨论】:

  • my_list = [1,2,3]; my_list[1:2] = [9,8,7] => [1, 9, 8, 7,3] 我认为用 list 替换 slice 时这是 python 的常见行为
  • 使用这个my_list[1:2] = [my_list[1]]
  • 我在那里编辑了一个答案,明确提到您可以在作业的两侧使用不同大小的列表。 “更不用说解释它为什么会这样工作了。” 你期望得到什么样的答案? stackoverflow.com/a/10623352/2301450stackoverflow.com/a/35632876/2301450 都是正确的。
  • 我希望得到一个详细说明我误解或不熟悉的语言机制的答案(这应该起作用的根本原因,例如“容器被实现为双链表,因此将单个元素设置为多个元素会自动起作用,因为 blah blah")。真正的答案原来是一个非常平凡的“因为我们为列表神奇地制作了这项工作,但没有别的”。 (当我注意到它不适用于 Numpy 数组时,我担心这会是答案。)

标签: python


【解决方案1】:

Slice assignment 将列表的指定部分替换为右侧的可迭代对象,该可迭代对象的长度可能与切片的长度不同。从表面上看问题,之所以这样,是因为方便。

您并没有真正分配给切片,即 Python 不会生成包含列表中指定值的切片对象,然后更改这些值。不起作用的一个原因是切片会返回一个新列表,因此此操作不会更改原始列表。

另见this question,它强调切片和切片分配是完全不同的。

【讨论】:

  • 这个答案和其他一些答案差不多,除了一句话,我觉得很重要:“之所以会这样,是因为方便”。列表切片分配是专门为具有这种行为而设计的,不一定是因为它是合乎逻辑的(我认为它根本不合逻辑),而是因为它很方便。谢谢!
【解决方案2】:

简答:

my_list[1:2] = my_list[1] 会将my_list 的第一个索引到第二个索引的内容替换为第一个索引中存在的内容 my_list

说明:

让我们看看两个切片操作,非常相似但完全不同

  1. 创建列表的副本并将其存储为变量

    some_variable = some_list[2:5]
    
  2. 替换了列表的内容就地,这也允许更改列表的长度。

    some_list[2:5] = [1, 2, 3, 4]
    

当您使用赋值运算符= 时,它会调用__setitem__ 函数。我们这里的重点是上面的案例 2。根据Python's Assignment Statement 文档:

如果目标是切片:参考中的主要表达式是 评估。它应该产生一个可变的序列对象(例如列表)。 分配的对象应该是相同类型的序列对象。 接下来,评估下限和上限表达式,在此范围内 他们在场;默认为零和序列的长度。这 bounds 应该计算为整数。如果任一界为负,则 序列的长度被添加到它。结果边界被剪裁为 介于零和序列的长度之间,包括在内。最后, 要求序列对象将切片替换为 分配的序列。切片的长度可能与 分配序列的长度,从而改变 目标序列,如果目标序列允许的话。

在我们的例子my_list[1:2] = my_list[1] 中,python 也会调用__setitem__ 为:

my_list.__setitem__(slice(1,2,None), [2, 3, 4])

请参阅slice 文档以了解它的作用。

所以,当您执行my_list[1:2] = my_list[1] 时,您将my_list 的第一个索引到第二个索引的内容替换为my_list 的第一个索引中存在的内容,即[2, 3, 4]


我想现在我们可以回答为什么你的假设不正确了:

  • 将 [2, 3, 4] 放到列表的第二个位置(它已经在的位置)

没有。因为__setitem__ 不是在索引上调用,而是在您传递的索引的slice 上调用。

  • 尝试将三个值(即 2,3,4)放入长度仅为 1 的容器(即 my_list[1:2])中,导致出现某种“太多值无法解包”错误。

再次不。因为创建容器的索引范围已替换为新的值集。

【讨论】:

    【解决方案3】:

    你正在做的是slice assignment

    也可以分配给切片,这甚至可以改变列表的大小或完全清除它

    my_list[1:2] = my_list[1]

    这会将my_list 的切片替换为my_list[1] 的内容。

    通过在赋值运算符= 的左侧指定my_list[1:2],您是在告诉Python 您要使用切片赋值。

    my_list[1:2] = my_list[1] 等价于my_list.__setitem__(slice(1, 2, None), my_list[1])

    slice(1, 2, None) 中,1 是开始,2 是停止,None 是步骤并且是可选的。

    【讨论】:

      【解决方案4】:

      这是来自Python Language Reference的相关位

      如果目标是切片:引用中的主要表达式是 评估。它应该产生一个可变的序列对象(例如列表)。 分配的对象应该是相同类型的序列对象。 接下来,评估下限和上限表达式,在此范围内 他们在场;默认为零和序列的长度。这 bounds 应该计算为整数。如果任一界为负,则 序列的长度被添加到它。结果边界被剪裁为 介于零和序列的长度之间,包括在内。最后, 要求序列对象将切片替换为 分配的序列。切片的长度可能与 分配序列的长度,从而改变 目标序列,如果目标序列允许的话。

      这种行为在质量上是有意义的,因为当您对列表进行切片时,您会得到一个子 列表,因此将其替换为另一个列表不应增加嵌套级别。允许它改变列表的长度是一种设计选择。如您的 numpy 示例所示,其他选择也是可能的。

      【讨论】:

        【解决方案5】:

        你正在做的是通过切片插入一个元素。我将分部分解释一切。不仅仅是插入,它还可以解释为在所需范围内切片目标列表后将项目添加到列表中。现在详细解释每一行:

        my_list[1:2]
        

        那部分就像对 Python 说的那样; “我想从索引 1 到 2 之前的索引获取值(切片)(不包括 2,稍后我将用另一个示例进行解释)”。之后,您为这些值分配一个值:

        my_list[1:2] = my_list[1] #The same as my_list[1:2] = [2,3,4]
        

        现在您知道第一部分的作用,接下来将在“=”运算符的右侧添加项目,以便您可以这样解释它; “我想从索引 1 切片到 2 之前的所有内容(同样,不包括索引 2),然后添加我的列表 [2,3,4]”。现在又来了一个例子,希望你能更好地理解。

        problemList = [1, [2, 3, 4], 5]
        problemList[1:2] = problemList[1] #The same as problemList[1:2] = [2,3,4]
        analogProblemL = [1] + [2,3,4] + [5] #Output : [1,2,3,4,5]
        
        insertList = [12,13,14]
        myList = [1, [2, 3, 4], 5,6,7,8,9]
        myList[3:6] = insertList
        analogFstList = [1,[2,3,4] ,5] + insertList + [9] #Output : [1,[2,3,4],5,12,13,14,9]
        
        myScnList = [1, [2, 3, 4], 5]
        myScnList[1:3] = [2,3,4]
        analogScnList = [1] + [2,3,4] + [5] #Output : [1,2,3,4,5]
        

        接下来的几行就像是动画帧一样,所以更容易解释:

        [1,2,3,4,5] #List before line of code: myList[1:3] = [12,13,14]
        [1,|2,3|,4,5] #The moment where you slice the list with: myList[1:3]. Please notice that the characters used '|' are for representing the slice.
        [1] + [12,13,14] + [4,5] #After assigning what the slice should be changed for. It's like excracting from the whole list the values [2,3] and changing it for [12,13,14].
        [1,12,13,14,4,5] #Final list after running the code.
        

        用于此答案的一些参考资料: http://effbot.org/zone/python-list.htm Understanding Python's slice notation How assignment works with python list slice

        希望对你有用。

        【讨论】:

          【解决方案6】:

          您在此处尝试的称为 Slice Assingnment。在 python 中,可以将一个可迭代对象(在您的情况下为my_list[1])分配给另一个可迭代对象的切片(在您的情况下为my_list[0:1])。让我们通过一些例子来理解它的真正含义:

           >>> l = [1,2,3,4,5]
           >>> b = [6,7,8]
           >>> l[0:3] = b
           >>> l
           >>> [6, 7, 8, 4, 5]
          

          所以这里发生的事情是列表l0,1,2 索引部分是 覆盖元素1,2,3被列表b6,7,8的元素替换。但是在这种情况下,切片和替换元素的大小碰巧是相等的。

          那么当切片大小和要替换的迭代不相等时会发生什么

          >>> l = [1,2,3,4,5]
          >>> b = [6,7,8]
          >>> l[0:4] = b
          >>> l 
          >>> [6,7,8,5]
          

          请注意,此操作没有产生任何错误,相反,它只是复制了整个切片部分可用的任何元素。在这种情况下,切片元素被1,2,3,4 替换为6,7,8

          在前面的示例中,要替换的可迭代对象较小。如果切片部分更小会发生什么

          >>> l = [1,2,3,4,5]
          >>> b = [6,7,8]
          >>> l[0:1] = b
          >>> l 
          >>> [6,7,8,2,3,4,5]
          

          所以现在我们可以看到只有第一个元素被整个可迭代的b 替换。

          您还可以使用此行为来删除列表的特定部分(我觉得在某些情况下很方便)。

           >>> l = [1,2,3,4,5]
           >>> l[0:2] = []
           >>> l 
           >>> [3,4,5]
          

          这里删除前两个元素非常方便。

          因此,您问题中的示例与我在上面发布的示例类似,只是在您的情况下还有一个解包列表值的附加步骤。每次将列表分配给另一个列表时,都会发生拆包列表值。一个简短的例子

          >>> l = [[1]]
          >>> a = []
          >>> a = l[0]
          >>> a
          >>> [1]
          

          你现在的例子:

          #Replace the slice [0:1] with my_list[1] perform unpacking list values as well
          >>> my_list[1:2] = my_list[1]  
          >>> [1,2,3,4,5]
          

          另请注意,只有将可迭代对象分配给切片时,才能进行切片分配。如果您尝试将 int 或不可迭代的内容分配给切片,python 将引发错误。

          >>> l = [1,2,3,4,5]
          >>> b = [6,7,8]
          >>> l[0:1] = b[1]
          >>> Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
           TypeError: can only assign an iterable
          

          这就是为什么在您的情况下my_list[1] 不会引发错误,因为它是可迭代的。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-06-06
            • 2019-10-01
            • 1970-01-01
            • 2019-05-23
            • 2021-05-30
            • 2014-03-15
            • 2015-11-19
            相关资源
            最近更新 更多