使用 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
也不会调用...
我想知道切片是否会创建列表的副本以及我们是否对切片使用 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 shoulddel
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
也不会调用...