使用 del with slicing 是否先创建一个新对象然后删除?会到位吗?

Does using del with slicing creates a new object first and then deletes? Will it be in-place?

我想知道切片是否会创建列表的副本以及我们是否对切片使用 del。那么它不会创建一个副本,即首先从给定切片的列表中创建一个新对象,然后删除它吗?

因此,如果一个程序是真正就地的,那么我们就不能使用切片。

# suppose if I use something like this:
a = [1,2,3,4,5,6,7,8,9,0]
del a[5:]

那么,这个 del [a:] 不会在给定的切片 a[5:] 中创建 a 的副本,然后删除它吗?

是这样的,不会是就地操作吧,因为我们这里用的是切片。

但是

a = [1,2,3,4,5,6,7,8,9,0]
for i in range(5,len(a),-1):
    del a[i]

这个操作是就地的,因为我们没有使用切片。

所以这应该更快吧?因为我们不用再经历新建切片直接删除对象的痛苦

但是我查了一下是相反的:

%%timeit
a = [x for x in range(1000)]
del a[500:]
> 45.8 µs ± 3.05 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%%timeit
a = [x for x in range(1000)]
for i in range(999,499,-1):
    del a[i]
> 92.9 µs ± 3.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

现在,如果我们假设 del a[500:] 先创建一个切片然后删除,那么它不是真正的就地,而对同一任务使用 for 循环是就地吗? 但是为什么循环要花更多的时间来完成任务?

此外,如果以防万一,我假设它们都以某种方式就位并且 del a[500:] 不会在该索引处创建 a 的切片副本并且 del 直接进入在索引 500 处递归删除元素,那么这条语句中的 a[500:] 到底是什么?只是告诉del应该从哪里删除?

如果是,那不就是一片吗?

我参考了一些链接(例如下面的链接)来找到这个问题的答案,但是从任何专门针对 in-place 操作的解释中我都不是很清楚。

What does "del" do exactly?

then what is exactly a[500:] in this statement? Is it just to tell where should del delete from?

是的。

Then won't it create a copy, i.e. a new object from the list for a given slice first and then delete it?

没有创建副本。这就是为什么 del 是一个关键字,而不是一个内置函数:它是一种特殊的语法,在上下文中以不同的方式解释 a[5:] 部分。 (如果它是一个函数,那么 a[5:] 必须在函数被调用之前被评估;而且这个函数也不可能工作,因为它会被提供一个单独的 value 而不是知道要对哪个原始对象进行操作。)

But I checked and they both take almost equal time:

for i in range(500,1000,-1):

小心;这是一个空循环。您可能打算 for i in range(1000, 500, -1),但这也不正确 - 它应该是 for i in range(999, 499, -1) 以获得匹配的行为。现在你明白为什么切片语法支持 del :)

无论如何,不​​知道你的计时结果。

重要的是要理解这是两个不同的东西:

del name

del container[index]

第一个从当前命名空间中删除一个名称。 del 使用任意表达式,它 不删除对象 ,它删除 名称 :

>>> del [1,2,3]
  File "<stdin>", line 1
SyntaxError: cannot delete literal
>>> del list()
  File "<stdin>", line 1
SyntaxError: cannot delete function call

然而,

del container[index]

将调用

container.__delitem__(index)

可以随心所欲,可以就地也可以不地。或者什么都不做...

另一方面

container[index]

调用

container.__getitem__(index)

同样,它可以为所欲为。它可以 return 一个副本,或者如果您想以这种方式实现它,它可以就地工作。

也许,这很有启发性:

>>> class Container:
...     def __getitem__(self, item):
...         print('Container.__getitem__ called with', item)
...     def __delitem__(self, item):
...         print('Container.__delitem__ called with', item)
...
>>> container = Container()
>>> container[:]
Container.__getitem__ called with slice(None, None, None)
>>> del container[:]
Container.__delitem__ called with slice(None, None, None)
>>> del container

请注意,del container 也不会调用...