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
为什么创建者允许这种特殊的行为会导致副本?
del和作业的设计始终如一,它们只是没有设计您期望它们的方式。del永远不会删除对象,它删除名称/引用(对象删除仅间接发生,是删除对象的重新卡/垃圾收集器(;同样,分配运算符永远不会复制对象,它总是创建/更新名称/参考。
DEL和分配操作员采用参考规范(类似于C中的LVALUE的概念,尽管详细信息有所不同(。此参考规范是变量名(普通标识符(,__setitem__
键(Square Bracket中的对象(或__setattr__
名称(DOT之后的标识符(。此lvalue不会像表达式一样评估,因为这样做将使不可能分配或删除任何内容。
考虑:
之间的对称性p[:] = [1, 2, 3]
和
del p[:]
在这两种情况下,p[:]
都起作用,因为它们都被评估为LVALUE。另一方面,在以下代码中,p[:]
是一个完全评估为对象的表达式:
q = p[:]
迭代器上的 del
只是用索引作为参数的 __delitem__
的调用。就像括号呼叫[n]是拨打Inderator实例上的__getitem__
方法n。
因此,当您调用p[:]
时,您正在创建一系列项目,当您调用del p[:]
时,您将del/__ delitem__映射到该序列中的每个项目。
正如其他人所说的;p[:]
删除p
中的所有项目;但不会影响Q。要进一步详细介绍列表文档,请参阅此信息:
所有切片操作返回包含所请求的新列表 元素。这意味着以下切片返回一个新的(浅( 列表的副本:
>>> 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种不同的上下文中使用slice-syntax:
- 访问,即
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
用于非法索引以允许正确检测序列的末端。
object.__setitem__(self, key, value)
呼叫将分配实施到
self[key]
。与__getitem__()
相同的注释。仅当对象支持对键的值更改,或者可以添加新密钥时,或者如果可以更换元素,则应为映射实现这一点。对于不正确的键值,应提出相同的例外,与__getitem__()
方法。
object.__delitem__(self, key)
呼叫以实现
self[key]
的删除。与__getitem__()
相同的说明。仅当对象支持去除密钥时,或在可以从序列中删除元素时,才应为映射实现。对于不正确的键值,应提出相同的例外,与__getitem__()
方法。
(强调我的(
因此,从理论上讲,任何容器类型都可以按照需要实施。但是,许多容器类型都遵循列表 - 实践。
我不确定您是否想要这种答案。用文字来说,对于p [:],它的意思是"遍历P的所有元素"。如果您在
中使用它q=p[:]
然后可以将其读为"迭代所有p的元素并将其设置为q"。另一方面,使用
q=p
只是意味着"将p的地址分配给q"或"使Q作为p"指针"将P"指向P",如果您来自其他可以单独处理指针的语言,这会造成混淆。
因此,在DEL中使用它,例如
del p[:]
仅表示"删除p的所有元素"。
希望这会有所帮助。
历史原因,主要是。
在Python的早期版本中,迭代器和发电机并不是真正的事情。使用序列的大多数方法刚刚返回列表:例如,range()
返回了包含数字的完全结构的列表。
因此,在表达式的右侧使用切片以返回列表是有意义的。a[i:j:s]
返回了一个新列表,其中包含a
中选定的元素。因此,分配右侧的a[:]
将返回包含a
元素的新列表,即浅副本:当时这是完全一致的。
另一方面,表达式的左侧的括号始终修改原始列表:那是a[i] = d
设置的先例,该先例后面是del a[i]
,然后由del a[i:j]
设置。
时间过去了,复制价值和实例化的新列表被视为不必要且昂贵。如今,range()
返回一个仅根据要求产生每个数字的发电机,并且在切片上进行迭代可能会以相同的方式工作,但是copy = original[:]
的成语过于固定在历史文物中。
在Numpy中,情况并非如此:ref = original[:]
将进行参考而不是浅副本,这与del
和分配数组的工作方式一致。
>>> a = np.array([1,2,3,4])
>>> b = a[:]
>>> a[1] = 7
>>> b
array([1, 7, 3, 4])
Python 4,如果发生过,可能会效仿。正如您所观察到的那样,它与其他行为更加一致。