【问题标题】:how del list[:] works [duplicate]del list[:] 如何工作[重复]
【发布时间】:2019-11-29 06:49:00
【问题描述】:

list[:] 创建列表的副本,那么为什么del list[:] 删除所有列表项?

不应该删除列表的副本吗?

【问题讨论】:

  • 将其视为list[:]“返回”列表中的所有项目(并且恰好将它们返回到新列表中)。那么del list[:]清除列表中的相同项目就更明显了。
  • 因为表达式list[:] 与语句del list[:] 不同。前者不会改变列表,而后者会。同样,list[x:y] = z 是一个变异列表。请注意,del 从不删除对象。它会删除名称。
  • @juanpa.arrivillaga 这个问题似乎是对令人困惑的陈述的完美反例del从不删除对象。它删除名称。”在切片删除中删除?在描述符的 __del__ 调用中删除了哪些名称,例如属性删除器?如果操作数是名称,它只会删除名称!
  • @wim 抱歉,是的,我看到我所说的内容令人困惑。澄清一下,我的意思是在del x 的简单情况下,del 关键字的作用是删除名称,而不是名称所引用的对象。使用del 除名称以外的任何内容将由该对象通过其__delitem__ 方法、__delattr__ 或通过类描述符上的__delete__ 方法定义。请注意,即使在这种情况下,它也不会删除对象,但是是的,我的陈述措辞有误。

标签: python python-3.x list copy


【解决方案1】:

因为a[:] 只是a 的一个副本,由与a 相同的对象组成,但与a 不同。它们的元素相同,但它们不同。

让我们创建一个列表并做一些id 检查:

a = [1, 2, 3]
print(id(a)) # 97731118088
print(id(a[:])) # 97731213576
print(id(a[:])) # 97731212104
print(id(a[:])) # 97731198600

注意 ids 的副本 a[:] 的变化。它是一个在每次调用时动态创建的对象,最重要的是,即使是a[:] is a[:] 而不是True!让我们看看他们的元素ids 得出一个结论:

print(id(a[0])) # 1648192992
print(id(a[:][0])) # 1648192992
a[0] is a[:][0] # True
a[1] is a[:][1] # True
a[2] is a[:][2] # True

因此,我们可以得出结论,a[:] 是一个由a 的元素组成的对象,但与a 不同,并且每次调用它时都是不同的对象。 aa[:] 的元素都是相同的,但它们本身并不相同。

所以del a[:] 所做的就是删除所有的元素 a。这样a 就发生了变异,你最终会得到一个空的a,即[]。但是,del a 将名称 a 从命名空间中完全删除,当您要求 Python 为您打印 a 时,您将得到一个 NameError: name 'a' is not defined

但是我们怎么知道呢?好吧,让我们通过在aa[:] 上拆解del 来获得一些观点:

让我们定义两个函数:

def deletion1(a):
    del a

def deletion2(a):
    del a[:]

让我们分解它们:

import dis

dis.dis(x = deletion1)

  1           0 DELETE_FAST              0 (a)
              2 LOAD_CONST               0 (None)
              4 RETURN_VALUE

dis.dis(x = deletion2)
  1           0 LOAD_FAST                0 (a)
              2 LOAD_CONST               0 (None)
              4 LOAD_CONST               0 (None)
              6 BUILD_SLICE              2
              8 DELETE_SUBSCR
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

dis documentation 表示第一个函数所做的DELETE_FAST 操作只是“删除本地co_varnames[var_num]”。这基本上是删除该名称 a 以便您无法再访问列表对象。请注意,这不会删除由名称 a 引用的对象,而只是删除其名称,以便 a 不再是对任何内容的引用。对象97731118088还是同一个列表,[1, 2, 3]

import gc
for obj in gc.get_objects():
    if id(obj) == 97731118088:
        print(obj)
        # [1, 2, 3]

另一方面,同样来自文档DELETE_SUBSCR“实现del TOS1[TOS]”,它基本上是“移除堆栈顶部并将结果推回堆栈的就地运算符”。这样一来,堆栈元素被删除,剩下的名称为a,它现在指的是一个元素被删除的列表,即只是一个“空壳”,如果你愿意的话。执行此操作后,a 变为 [],但仍具有与 97731118088 相同的 id。只是其元素通过就地删除而消失了。

【讨论】:

  • “因为 a[:] 不是列表 a 的副本。它只是其中的所有元素。”我不明白你在这里试图做出的区分。 a is not a[:]。准确地说,表达式a[:] 的结果是一个新的列表对象,它是名称a 引用的列表对象的副本(表示为完整切片)
  • @juanpa.arrivillaga 抱歉,措辞不好,我会编辑那篇文章。
  • @juanpa.arrivillaga 请查看更新后的答案,我试图通过查看ids 的细节并反汇编函数来做一个彻底的答案。
【解决方案2】:

答案是否定的,不应该。它旨在删除列表中的所有元素。查看the documentations.clear()(其中s 是可变序列类型,例如list)的结果是:

从 s 中删除所有项目(与 del s[:] 相同)

因此,del s[:]s.clear() 相同,因为它会删除 s 中的所有项目。

也许,如果您考虑到在后台调用的函数是__delitem__,这可能会更容易理解。来自the docs

调用以实现删除self[key]。与__getitem__() 相同的注释。如果对象支持删除键,则仅应为映射实现此功能,如果可以从序列中删除元素,则应为序列实现。对于不正确的键值,应引发与 __getitem__() 方法相同的异常。

考虑以下区别:

a = [1,2,3]
b = a

del a
# print(a) ## raises an error
print(b)   ## prints [1,2,3]

c = [1,2,3]
d = c

del c[:]
print(c)   ## prints []
print(d)   ## prints []

那么你为什么要del a[:] 这样做呢?好吧,将其视为删除列表切片的一个特例。例如,假设您要删除长列表a = list(range(40)) 的第 3、第 4 和第 5 个元素。使用切片符号和__delitem__ 这很容易,只需使用del a[3:6]。现在尝试对for 循环执行相同的操作,您很快就会发现它会变得非常麻烦。哎呀,只需尝试使用for 循环删除a 的所有项目(但不是a 本身!);)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    • 2019-04-18
    • 2019-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多