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)
ipa*_*eka 23
del在迭代器上的调用只是将__delitem__index作为参数。就像括号调用[n]是__getitem__对索引为n的迭代器实例上的方法的调用一样。
因此,当您调用时,p[:]您将创建一个项目序列,并在您调用时del p[:]将del / __ delitem__映射到该序列中的每个项目。
正如其他人所说的;p[:]删除中的所有项目p; 但是不会影响q。要进一步详细说明,列表文档仅涉及以下内容:
所有切片操作都返回一个包含所请求元素的新列表。这意味着以下切片将返回列表的新(浅)副本:
Run Code Online (Sandbox Code Playgroud)>>> squares = [1, 4, 9, 16, 25] ... >>> squares[:] [1, 4, 9, 16, 25]
因此,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[:]。
基本上,切片语法可以在3种不同的上下文中使用:
x = foo[:]foo[:] = xdel 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__()方法相同的例外。
(强调我的)
因此,从理论上讲,任何容器类型都可以实现其所需的功能。但是,许多容器类型都遵循列表实现。