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) # []
以上确认p[:]
在这两种情况下的工作方式不同。不是吗?
考虑到在以下代码中,我希望直接使用p
,而不是p
的副本,
p[0] = 111
p[1:3] = [222, 333]
print(p) # [111, 222, 333]
我感觉
del p[:]
与p[:]
一致,它们都引用原始列表
但是
q=p[:]
在p[:]
中(对于像我这样的新手)感到困惑,在这种情况下会导致出现新列表!
我的新手期望是
q=p[:]
应与
相同q=p
为什么创建者允许这种特殊行为来代替副本?
答案 0 :(得分:54)
del和分配的设计是一致的,只是没有按照您期望的方式设计它们。 del从不删除对象,它删除名称/引用(对象删除仅是间接发生的,是refcount /垃圾收集器删除对象);同样,赋值运算符从不复制对象,它总是在创建/更新名称/引用。
del和赋值运算符采用参考规范(类似于C中的左值的概念,尽管细节有所不同)。该参考规范可以是变量名(普通标识符),__setitem__
键(方括号中的对象)或__setattr__
名称(点号后的标识符)。该左值不像表达式那样求值,否则将无法分配或删除任何内容。
考虑以下两者之间的对称性:
p[:] = [1, 2, 3]
和
del p[:]
在两种情况下,p[:]
的工作方式相同,因为它们都被视为左值。另一方面,在下面的代码中,p[:]
是一个完全评估为对象的表达式:
q = p[:]
答案 1 :(得分:23)
>>> a = np.array([1,2,3,4])
>>> b = a[:]
>>> a[1] = 7
>>> b
array([1, 7, 3, 4])
只是对以索引为参数的del
的调用。就像括号调用[n]是对索引为n的迭代器实例上的__delitem__
方法的调用一样。
因此,当您调用__getitem__
时,您将创建一个项目序列,而当您调用p[:]
时,您会将del / __ delitem__映射到该序列中的每个项目。
答案 2 :(得分:7)
正如其他人所说; p[:]
删除p
中的所有项目;但是不会影响q。要进一步详细了解列表docs,请参考:
所有切片操作都返回一个包含请求的新列表 元素。这意味着以下切片返回一个新的(浅) 列表的副本:
>>> 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
在copy
模块中对此进行了更好的解释:
浅表副本构造一个新的复合对象,然后(到 在可能的范围内)将引用插入其中 原始的。
尽管del语句是在列表/切片上递归执行的:
删除目标列表会从左到右递归删除每个目标。
因此,如果我们使用del p[:]
,则会通过遍历每个元素来删除p
的内容,而q
并未如前所述进行更改,尽管它具有相同的项目:
>>> del p[:]
>>> p
[]
>>> q
[1, 2, 3]
实际上,列表文档中的list.clear
方法中也引用了此方法:
列表。 copy()
返回列表的浅表副本。等效于
a[:]
。列表。 clear()
从列表中删除所有项目。等效于
del a[:]
。
答案 3 :(得分:6)
基本上,分片语法可以在3种不同的上下文中使用:
x = foo[:]
foo[:] = x
del foo[:]
在这些情况下,放在方括号中的值只是选择项目。旨在在以下每种情况下一致使用“切片”:
因此,x = foo[:]
在foo
中获取所有元素,并将它们分配给x
。这基本上是一个浅表副本。
但是foo[:] = x
将foo
中的所有元素替换为x
中的元素。
,并且在删除del foo[:]
时将删除foo
中的所有元素。
但是,此行为是可自定义的,如3.3.7. Emulating container types所述:
object.__getitem__(self, key)
被要求实施对
self[key]
的评估。对于序列类型,可接受的键应为整数和切片对象。请注意,负索引的特殊解释(如果类希望模拟序列类型)取决于__getitem__()
方法。如果密钥的类型不合适,则可能引发TypeError
;如果该值超出了该序列的索引集(在对负值进行任何特殊解释之后),则应引发IndexError
。对于映射类型,如果缺少键(不在容器中),则应引发KeyError
。注意
for
循环期望为非法索引引发IndexError
,以便正确检测序列的结尾。
object.__setitem__(self, key, value)
被调用以实现对
self[key]
的分配。与__getitem__()
相同的注释。仅当对象支持对键的值进行更改,或者如果可以添加新的键,或者对于序列(如果可以替换元素)时,才应对映射实现。对于不正确的键值,应该引发与__getitem__()
方法相同的例外。
object.__delitem__(self, key)
呼叫删除
self[key]
。与__getitem__()
相同的注释。如果对象支持删除键,则仅应针对映射实现;如果可以从序列中删除元素,则应仅对序列实现。对于不正确的键值,应该引发与__getitem__()
方法相同的例外。
(强调我的)
因此,从理论上讲,任何容器类型都可以实现所需的实现。但是,许多容器类型都遵循列表实现。
答案 4 :(得分:2)
我不确定您是否需要这种答案。换句话说,对于p [:],它意味着“遍历p的所有元素”。如果在
中使用它(import 'java.time.format.DateTimeFormatter)
(import 'java.time.temporal.TemporalQuery)
(import 'java.time.LocalDate)
(let [dtf (DateTimeFormatter/ISO_LOCAL_DATE)]
(.parse dtf "2019-04-04"
(reify TemporalQuery
(queryFrom [this temporal]
(LocalDate/from temporal)))))
然后可以将其理解为“使用p的所有元素进行迭代并将其设置为q”。另一方面,使用
q=p[:]
仅仅意味着“将p的地址分配给q”或“使q成为p的指针”,如果您来自单独处理指针的其他语言,这会造成混淆。
因此,在del中使用它,例如
q=p
仅表示“删除p的所有元素”。
希望这会有所帮助。
答案 5 :(得分:2)
主要是历史原因。
在Python的早期版本中,迭代器和生成器并不是真正的东西。使用序列的大多数方法只是返回列表:例如,range()
返回包含数字的完全构造的列表。
因此,在表达式的右侧使用切片时,返回列表是有意义的。 a[i:j:s]
返回了一个新列表,其中包含来自a
的选定元素。因此,在任务右侧的a[:]
将返回一个包含a
的所有元素的新列表,即浅表副本:这在当时是完全一致的。
另一方面,表达式的左侧的括号始终会修改原始列表:这是a[i] = d
设置的先例,而该先例后面是{{1 }},然后按del a[i]
。
时间流逝,在各处复制值和实例化新列表被认为是不必要且昂贵的。如今,del a[i:j]
返回一个生成器,该生成器仅按要求生成每个数字,并且在切片上进行迭代可能以相同的方式工作,但是range()
的习语已被根深蒂固地视为历史产物。
顺便说一句,在Numpy中,情况并非如此:copy = original[:]
将进行引用而不是浅表副本,这与ref = original[:]
和数组赋值的工作方式是一致的。>
del
Python 4(如果曾经发生过)可能会效仿。正如您所观察到的,它与其他行为更加一致。