为什么p [:]设计为在这两种情况下工作不同?

inq*_*One 51 python slice python-3.x

p = [1,2,3]
print(p) # [1, 2, 3]

q=p[:]  # supposed to do a shallow copy
q[0]=11
print(q) #[11, 2, 3] 
print(p) #[1, 2, 3] 
# above confirms that q is not p, and is a distinct copy 

del p[:] # why is this not creating a copy and deleting that copy ?
print(p) # [] 
Run Code Online (Sandbox Code Playgroud)

以上确认p[:]在这两种情况下工作方式不同。是不是

考虑到在以下代码中,我希望直接使用p而不是的副本p

p[0] = 111
p[1:3] = [222, 333]
print(p) # [111, 222, 333]
Run Code Online (Sandbox Code Playgroud)

我觉得

del p[:] 
Run Code Online (Sandbox Code Playgroud)

与一致p[:],它们都引用原始列表,但是

q=p[:] 
Run Code Online (Sandbox Code Playgroud)

令(对像我这样的新手)感到困惑,因为p[:]在这种情况下会生成新列表!

我的新手期望是

q=p[:]
Run Code Online (Sandbox Code Playgroud)

应该与

q=p
Run Code Online (Sandbox Code Playgroud)

为什么创建者允许这种特殊行为来产生副本?

Lie*_*yan 57

del和分配的设计是一致的,只是设计不像您期望的那样。del从不删除对象,它删除名称/引用(对象删除仅是间接发生的,是refcount /垃圾收集器删除对象);同样,赋值运算符从不复制对象,它总是在创建/更新名称/引用。

del和赋值运算符采用参考规范(类似于C中的左值的概念,尽管细节不同)。该参考规范可以是变量名(普通标识符),__setitem__键(方括号中的对象)或__setattr__名称(点后的标识符)。该左值的计算方式不像表达式那样,因为这样做将无法分配或删除任何内容。

考虑以下两者之间的对称性:

p[:] = [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

del p[:]
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,p[:]它们的工作方式相同,因为它们都被评估为左值。另一方面,在下面的代码中,p[:]是一个完全评估为对象的表达式:

q = p[:]
Run Code Online (Sandbox Code Playgroud)

  • @brainOverflow numpy希望尽可能高效,并且尽可能简洁且易于阅读。由于将优化numpy代码,为什么使最常见的代码更冗长?numpy并不是作为一个通用框架诞生的,它是为具有一定专业知识水平的人设计的,因此他们做出的选择与Guido在设计python语言时所做的选择不同 (4认同)
  • p [:]与numpy类似,但有所不同。有关视图,请参见[docs](https://www.numpy.org/devdocs/user/quickstart.html#view-or-shallow-copy)。“切片数组会返回一个视图”,而视图是“一个看相同数据的新数组对象” (3认同)

ipa*_*eka 23

del在迭代器上的调用只是将__delitem__index作为参数。就像括号调用[n]是__getitem__对索引为n的迭代器实例上的方法的调用一样。

因此,当您调用时,p[:]您将创建一个项目序列,并在您调用时del p[:]将del / __ delitem__映射到该序列中的每个项目。


Jab*_*Jab 7

正如其他人所说的;p[:]删除中的所有项目p; 但是不会影响q。要进一步详细说明,列表文档仅涉及以下内容:

所有切片操作都返回一个包含所请求元素的新列表。这意味着以下切片将返回列表的新(浅)副本:

>>> squares = [1, 4, 9, 16, 25]
...
>>> squares[:]
[1, 4, 9, 16, 25]
Run Code Online (Sandbox Code Playgroud)

因此,q=p[:]创建一个(浅)副本p作为一个单独的列表,但是在进一步检查后,它的确指向内存中一个完全独立的位置。

>>> p = [1,2,3]
>>> q=p[:]
>>> id(q)
139646232329032
>>> id(p)
139646232627080
Run Code Online (Sandbox Code Playgroud)

这在copy模块中有更好的解释:

浅表副本将构造一个新的复合对象,然后(在可能的范围内)将对原始对象中找到的对象的引用插入其中。

尽管del语句是对列表/切片递归执行的:

目标列表的删除会从左到右递归删除每个目标。

因此,如果我们使用del p[:]我们p通过迭代每个元素来删除的内容,而q并未如前所述进行更改,则它引用一个单独的列表,尽管具有相同的项目:

>>> del p[:]
>>> p
[]
>>> q
[1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

实际上,列表文档中的list.clear方法也引用了此方法:

清单。复制()

返回列表的浅表副本。等同于a[:]

清单。明确()

从列表中删除所有项目。等同于del a[:]

  • 不会在该列表上递归执行del语句。文档讨论的是目标列表,而不是示例中的一个list + slice。考虑“ del parrot,垃圾邮件,圣杯”-这里的目标列表具有从左到右删除的三个元素,首先是“ parrot”,然后是“ spam”,然后是“ grail”。在`del p [:]`中,只有一个目标被删除:`p [:]`。 (2认同)

MSe*_*ert 6

基本上,切片语法可以在3种不同的上下文中使用:

  • 访问,即 x = foo[:]
  • 设置,即 foo[:] = x
  • 删除,即 del foo[:]

在这些情况下,放在方括号中的值只是选择项目。旨在在以下每种情况下一致使用“切片”:

  • 所以x = foo[:]得到的所有元素foo,并为它们分配到x。这基本上是一个浅表副本。

  • 但是,foo[:] = x将取代所有元素foo与元素x

  • 和删除时del foo[:]将删除所有元素foo

但是,此行为是可自定义的,如3.3.7所述。模拟容器类型

object.__getitem__(self, key)

要求对实施评估self[key]。对于序列类型,可接受的键应为整数和切片对象。请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于该__getitem__()方法。如果密钥类型不合适,则TypeError可能会提出该密钥;如果该值超出了该序列的索引集(在对负值进行任何特殊解释之后),IndexError则应提出该值。对于映射类型,如果缺少键(不在容器中),KeyError则应引发。

注意

for循环期望IndexError会为非法索引引发an ,以正确检测序列的结尾。

object.__setitem__(self, key, value)

被调用以实现对的赋值self[key]。与相同的注释__getitem__()。仅当对象支持对键的值的更改或可以添加新键的情况下,或者对于序列(如果可以替换元素)时,才应对映射实现。对于不正确的键值,应提出与__getitem__()方法相同的例外。

object.__delitem__(self, key)

调用以实现的删除self[key]。与相同的注释__getitem__()。如果对象支持删除键,则应仅对映射实现;如果可以从序列中删除元素,则应对序列实现。对于不正确的键值,应提出与__getitem__()方法相同的例外。

(强调我的)

因此,从理论上讲,任何容器类型都可以实现其所需的功能。但是,许多容器类型都遵循列表实现。