【问题标题】:Does using del with slicing creates a new object first and then deletes? Will it be in-place?是否使用 del 切片先创建一个新对象然后删除?会到位吗?
【发布时间】:2020-11-15 16:40:55
【问题描述】:

我想知道切片是否会创建列表的副本,以及我们是否在切片上使用 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?

【问题讨论】:

    标签: python list memory slice del


    【解决方案1】:

    那么这个语句中的a[500:] 到底是什么?只是告诉del应该从哪里删除?

    是的。

    那么它不会创建一个副本,即首先从列表中为给定切片创建一个新对象,然后将其删除吗?

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

    但我查了一下,它们都花费了几乎相同的时间:

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

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

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

    【讨论】:

      【解决方案2】:

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

      del name
      

      del container[index]
      

      第一个从当前命名空间中删除一个名称del 与任意表达式一起工作,它不删除对象,它删除 names

      >>> 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)
      

      同样,它可以做任何你想做的事情。它可以返回一个副本,或者如果您想以这种方式实现它,它可以就地工作。

      也许,这很有启发性:

      >>> 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 也不会调用...

      【讨论】: