使用del不好吗?

Posted

技术标签:

【中文标题】使用del不好吗?【英文标题】:Is the use of del bad? 【发布时间】:2014-06-13 10:17:25 【问题描述】:

我通常在我的代码中使用del 来删除对象:

>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>> 

但我听说many people 说del 的使用是非pythonic。使用del 是不好的做法吗?

>>> array = [4, 6, 7, 'hello', 8]
>>> array[array.index('hello'):array.index('hello')+1] = ''
>>> array
[4, 6, 7, 8]
>>> 

如果不是,为什么在 python 中有很多方法可以完成相同的事情?一个比其他的好吗?

选项 1:使用del

>>> arr = [5, 7, 2, 3]
>>> del(arr[1])
>>> arr
[5, 2, 3]
>>> 

选项 2:使用list.remove()

>>> arr = [5, 7, 2, 3]
>>> arr.remove(7)
>>> arr
[5, 2, 3]
>>> 

选项 3:使用list.pop()

>>> arr = [5, 7, 2, 3]
>>> arr.pop(1)
7
>>> arr
[5, 2, 3]
>>> 

选项 4:使用切片

>>> arr = [5, 7, 2, 3]
>>> arr[1:2] = ''
>>> arr
[5, 2, 3]
>>> 

如果这个问题似乎是基于意见的,我很抱歉,但我正在为我的问题寻找一个合理的答案,如果我没有得到合适的答案,我会在 2 天后添加赏金。

编辑:

由于使用del 删除对象的某些部分有许多替代方法,所以del 剩下的一个独特因素是它能够完全删除对象:

>>> a = 'hello'
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
'hello'
>>> 

但是,使用它来“取消定义”对象有什么意义?

另外,为什么下面的代码会改变这两个变量:

>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> 

但是del语句没有达到同样的效果?

>>> a = []
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[]
>>> 

【问题讨论】:

很多人 [需要引用]。谁说的,说这句话的背景是什么? 问题是所有这些操作都会修改输入列表并且是线性时间的。这有点浪费,因为您可以创建一个新列表,同时在线性时间内删除元素,同时保留旧列表 btw Python 列表是列表而不是数组; python中也有数组,所以最好不要将列表变量称为“数组”。 deluse 可能还不错,但是删除列表索引的语法 del 的存在是该语言的一个尴尬特性,就像 @ 987654342@,这两个都惹恼了那些抱怨它与其他流行语言不符而没有明确理由的新手。 @SargeBorsch 所以如果有人有一个可以一步完成的三重嵌套循环,我们应该告诉他们切换到 C,对吗? 【参考方案1】:

其他答案是从技术角度来看它(即修改列表的最佳方法是什么),但我想说的是人们推荐的(更)更重要的原因,例如切片,是它不修改原始列表。

这反过来的原因是,通常,列表来自某个地方。如果您修改它,您可能会在不知不觉中导致严重且难以检测的副作用,这可能会导致程序其他地方出现错误。或者即使您没有立即引起错误,您也会使您的程序整体更难理解、推理和调试。

例如,列表推导式/生成器表达式很好,因为它们永远不会改变它们传递的“源”列表:

[x for x in lst if x != "foo"]  # creates a new list
(x for x in lst if x != "foo")  # creates a lazy filtered stream

这当然通常更昂贵(内存方面),因为它会创建一个新列表,但使用这种方法的程序在数学上更纯粹,更容易推理。并且使用惰性列表(生成器和生成器表达式),甚至内存开销也会消失,并且只按需执行计算;请参阅http://www.dabeaz.com/generators/ 了解精彩介绍。在设计程序时,您不应该过多考虑优化(请参阅https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil)。此外,从列表中删除一个项目非常昂贵,除非它是一个链表(Python 的list 不是;有关链表,请参阅collections.deque)。


事实上,无副作用的函数和immutable data structures 是函数式编程的基础,这是一种非常强大的编程范式。

但是,在某些情况下,可以就地修改数据结构(即使在 FP 中,if the language allows it),例如当它是本地创建的,或者从函数的输入中复制时:

def sorted(lst):
    ret = list(lst)  # make a copy
    # mutate ret
    return ret

——从外部看,这个函数似乎是一个纯函数,因为它不修改它的输入(并且也只依赖于它的参数而不依赖于其他任何东西(即它没有(全局)状态),这是另一个要求PureFunction)。

所以只要你知道自己在做什么,del 绝不是坏事;但要极其小心地使用任何类型的数据突变,并且仅在必要时才使用。总是从效率可能较低但更正确且数学上更优雅的代码开始。

...学习Functional Programming :)

附注请注意,del 也可用于删除局部变量,从而消除对内存中对象的引用,这对于任何与 GC 相关的目的通常都很有用。


回答你的第二个问题:

至于您关于 del 完全删除对象的问题的第二部分 - 情况并非如此:实际上在 Python 中,甚至不可能告诉解释器/VM 删除一个从内存中删除对象,因为 Python 是一种垃圾收集语言(如 Java、C#、Ruby、Haskell 等),它是运行时决定删除什么以及何时删除。

相反,del 在这样的变量(而不是字典键或列表项)上调用时会做什么:

del a

是它删除局部(或全局)变量,而删除变量指向的内容(Python中的每个变量都持有指向其内容的指针/引用而不是内容本身)。事实上,由于局部变量和全局变量在底层存储为字典(参见locals()globals()),del a 相当于:

del locals()['a']

del globals()['a'] 应用于全局。

如果你有:

a = []
b = a

您正在制作一个列表,将对其的引用存储在 a 中,然后制作该引用的另一个副本并将其存储到 b 中,而无需复制/触摸列表对象本身。因此,这两个调用影响同一个对象:

a.append(1)
b.append(2)
 # the list will be [1, 2]

而删除b 与触摸b 指向的内容没有任何关系:

a = []
b = a
del b
# a is still untouched and points to a list

此外,即使您在对象属性上调用del(例如del self.a),您实际上仍在修改字典self.__dict__,就像您实际修改locals()/globals() 一样del a.

附:正如 Sven Marcnah 指出的那样,del locals()['a'] 在函数内部实际上并没有删除局部变量a,这是正确的。这可能是由于locals() 返回了实际本地人的副本。但是,答案仍然普遍有效。

【讨论】:

"这当然通常更慢" 也许对于这个特定场景来说甚至不是这种情况,因为从列表中间删除某些东西也是线性时间。无论如何,与收益相比,成本较低 "del a 等价于del locals()['a']":不是真的,因为the latter does nothing inside a function。您不能以任何有意义的方式修改 locals() 返回的字典。 (不过你可以修改globals()。) @NiklasB。如果您只删除一件事,del 可能比副本更快(它可能需要移动元素,并且最多需要移动 100% 的元素)。然而,就像 Lays 芯片一样,有时人们不能只停留在一个 del 上,真的应该使用列表解析或过滤器。【参考方案2】:

del 只是改变变量,这有时是不必要的。因此,您的上述解决方案可能会更好。但是,del 是“销毁”变量并永久删除它们的唯一方法:

>>> a = 9
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

此外,您可以从字典中删除项目:

>>> dict = 1: 6
>>> dict[1]
6
>>> del(dict[1])
>>> dict

>>> 

【讨论】:

【参考方案3】:

Python 简单地包含了许多从列表中删除项目的不同方法。所有这些都在不同的情况下有用。

# removes the first index of a list
del arr[0]

# Removes the first element containing integer 8 from a list
arr.remove(8)

# removes index 3 and returns the previous value at index 3
arr.pop(3)

# removes indexes 2 to 10
del arr[2:10]

因此他们都有自己的位置。显然,当想要删除数字 8 时,示例 2 比 1 或 3 更好。因此,根据具体情况,这确实是有意义的,也是最合乎逻辑的。

编辑

arr.pop(3)del arr[3] 的区别在于 pop 返回被移除的项目。因此,它对于将删除的项目转移到其他数组或数据结构中很有用。否则两者在使用上没有区别。

【讨论】:

小说明:“删除列表的第一个索引”不应该是del arr[0]吗?除非我错过了del 的一些细微差别?【参考方案4】:

我想我从来没有听过任何人说del 是邪恶的,至少不比任何其他语言功能更邪恶。 del 和其他方法之间的问题实际上归结为您的用例。以下情况非常适合del

    从当前作用域中删除变量。你为什么想做这个?想象一下,您正在声明一个计算包变量的模块,但该模块的使用者从不需要它。虽然您可以为它创建一个全新的模块,但这可能是矫枉过正或可能掩盖实际计算的内容。例如,您可能需要以下内容:

    GLOBAL_1 = 'Some arbitrary thing'
    GLOBAL_2 = 'Something else'
    
    def myGlobal3CalculationFunction(str1, str2):
        # Do some transforms that consumers of this module don't need
        return val
    
    GLOBAL_3 = myGlobal3CalculationFunction(GLOBAL_1, GLOBAL_2)
    # Mystery function exits stage left
    del myGlobal3CalculationFunction
    

    基本上没有人不同意在必要时使用del 从范围中删除变量。这同样适用于字典中的值,或者通过名称或类似的不可变引用(类属性、实例属性、字典值等)访问的几乎任何东西。

    另一种情况是您想从列表或类似的有序序列中删除项目。这在某些方面与第一种情况并没有什么不同(因为它们都可以作为键值容器访问,而列表恰好具有可靠排序的整数键)。在所有这些情况下,您都希望删除对该特定实例中存在的某些数据的引用(因为即使类也是类的实例)。您正在进行就地修改。

    有序索引和特殊索引是否意味着列表有什么不同?与列表的根本区别在于,进行就地修改会使您的所有旧密钥基本上无用,除非您非常小心。 Python 为您提供了非常语义化地表示数据的强大能力:与其拥有 [actor, verb, object] 的列表和映射索引,您可以拥有一个不错的 'actor' : actor, 'verb' : verb, 'object' : object 字典。这种访问通常有很多价值(这就是我们按名称而不是按编号访问函数的原因):如果顺序不重要,为什么要让它变得僵硬?如果您的订单很重要,您为什么要搞砸某些东西会使您对它的所有引用都无效(即元素位置、元素之间的距离)。

问题归结为为什么要按索引直接删除列表值。在大多数情况下,就地修改列表的单个元素的操作通过其他函数具有明显的实现。杀死具有给定值的项目?你remove它。实现队列还是堆栈?你pop它(不要锁定它)。减少列表中实例的引用计数? l[i] = None 也同样有效,您的旧索引仍然指向相同的东西。过滤元素?您filter 或使用列表理解。制作列表的副本,减去一些元素?你slice它。摆脱重复的、可散列的元素?如果您只需要遍历唯一元素一次,您可以list(set([])) 或查看itertools

摆脱所有这些情况后,您最终会得到大约两个使用del 的常见用例作为列表。首先,您可能会按索引删除随机元素。在很多情况下这可能有用,del 是完全合适的。其次,您存储了代表您在列表中的位置的索引(即,在走廊里从一个房间走到另一个房间,有时您会随机破坏一个房间,来自 Charlie Sheen 编程风格指南)。如果同一个列表有多个索引,这会变得很困难,因为使用del 意味着所有索引都需要相应地调整。这不太常见,因为您使用索引行走的结构通常不是您从中删除元素的结构(例如,游戏板的坐标网格)。但它确实会发生,例如循环遍历列表以轮询作业并删除已完成的作业。

这表明了按索引从列表中就地删除元素的根本问题:您几乎每次只做一个。如果你有两个元素的索引要删除,那么删除第一个?您的旧索引很有可能没有指向它以前的索引。列表用于存储顺序。由于del 改变了绝对顺序,你会被卡在列表中行走或跳跃。同样,有可靠的用例(例如,随机破坏),但还有很多其他情况是错误的。特别是在新的 Python 程序员中,人们在函数上使用while 循环会做一些可怕的事情(即循环直到找到与输入匹配的值,del 索引)。 Del 需要一个索引作为输入,一旦运行,所有引用该列表的现有索引都会引用完全不同的数据。如果维护多个索引,您可以看到这是一场维护噩梦。再说一次,这还不错。只是在实践中它很少是在 Python 中使用列表做事的最佳方式。

【讨论】:

【参考方案5】:

关于“编辑”中的问题,

>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[9]
>>>

这很容易解释,请记住:

>>> id(a) == id(b) 
True

ab指向内存中的同一个对象)并且python中的内存由GC管理。 在对象上调用del 时,您只需将其引用计数减1(同时从作用域中删除名称),当引用计数达到0 时对象将被销毁。在这种情况下,b 仍然持有引用到对象,因此它不会被破坏并且仍然可以访问。

您可以找到更多信息here

【讨论】:

【参考方案6】:

del 的使用本身还不错;但是,它有两个方面会导致特定的代码异味:

    这是一个副作用,是一系列步骤的一部分,本身没有意义。 del 可能出现在具有手动内存管理的代码中,这表明对 Python 作用域和自动内存管理的理解不足。就像with 语句比file.close 更习惯于处理文件句柄一样,使用范围和上下文比手动核对成员更习惯。

但这几乎不是经典——如果del 关键字真的“不好”,它就不会出现在语言的核心中。我只是想扮演恶魔的拥护者——解释为什么有些程序员可能会称它为“坏”,并且可能会给你一个反对的立场。 ;)

【讨论】:

del 是“完全一元的”??我认为您在 Haskell 的 IO monad(我同意,它在 FP/Haskell 世界中是“副作用”的半同义词)和副作用代码的一般概念;但是这种联系是完全误导的——如果一个不知道 Monad 是什么的人阅读了这个答案,一旦他看到正确使用了“monad”这个词,他就会完全被误导和迷失方向。 del 删除了名称绑定——这怎么没有意义? @EthanFurman 这是一个tree falls in the forest 的问题。 del 没有任何意义,除非有其他东西观察到它的影响。理想情况下,编译器会使用DCE 消除无意义的副作用命令,但这并不总是可行的。 del 在这方面并不是唯一的。 a = 1 + c 本身也没有意义,也没有任何代码在任何地方,除非并且直到它被系统的另一部分观察/使用。我认为没有理由像这样单独del @EthanFurman 然后争论整个问题的范围。正如我所说,我实际上并不认为 del 不好。【参考方案7】:

不,我认为使用del 一点也不坏。事实上,在某些情况下它基本上是唯一合理的选择,例如从字典中删除元素:

k = 'foo': 1, 'bar': 2
del k['foo']

可能问题在于初学者并不完全理解变量在 Python 中是如何工作的,所以del 的使用(或误用)可能会比较陌生。

【讨论】:

以上是关于使用del不好吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 NSLayoutAnchor 不好吗?

使用“is”来命名布尔变量是不好的做法吗?

使用“goto”语句不好吗?

使用 Null 来表示值是不好的做法吗?

在 void 方法中使用 return 是不好的做法吗?

使用 iframe 是不好的做法吗? iframe 可以像画布一样被翻译成图像吗?