del使用不好吗?

时间:2014-04-28 02:03:17

标签: python slice mutable side-effects

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

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

但我听说many peopledel的使用是单声道的。使用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天后加上奖金。< / p>

编辑:

由于使用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'
>>> 

但是,使用它来取消定义&#39;对象

此外,为什么以下代码会更改这两个变量:

>>> 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
[]
>>> 

7 个答案:

答案 0 :(得分:43)

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

反过来的原因通常是,列表来自某个地方。如果你修改它,你可能会出现非常糟糕和难以察觉的副作用,这可能会导致程序中的其他错误。或者即使你没有立即引起错误,你也会使你的程序更难理解和推理,并进行调试。

例如,list comprehensions / generator表达式很不错,因为它们永远不会改变它们传递的“source”列表:

[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

- 这个函数看起来是一个来自外部的纯函数,因为它不会修改它的输入(也只取决于它的参数而没有别的(即它没有(全局)状态),这是另一个要求有些东西是Pure Function)。

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

...并学习Functional Programming:)

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


回答您的第二个问题:

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

相反,调用变量(而不是字典键或列表项)时del执行的操作如下:

del a

删除本地(或全局)变量而变量指向的内容(Python中的每个变量都包含指向其内容的指针/引用而不是内容本身)。实际上,由于locals和globals被存储为字典(见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时。

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

答案 1 :(得分:12)

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 返回已删除的项目。因此,将删除的项目传输到其他数组或数据结构中非常有用。否则两者的使用没有区别。

答案 2 :(得分:8)

不,我不认为使用del是不好的。事实上,有些情况下它实际上是唯一合理的选择,比如从字典中删除元素:

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

也许问题是初学者并不完全理解变量在Python中是如何工作的,因此del的使用(或误用)可能不熟悉。

答案 3 :(得分:5)

使用del本身并不坏;然而,它有两个方面导致特定代码气味:

  1. 这是一种副作用,是一系列步骤的一部分,并且本身没有意义。
  2. 可能是del出现在具有手动内存管理的代码中,表明对Python范围和自动内存管理的理解不足。与with语句处理文件句柄比file.close更惯用的方式相同,使用范围和上下文比手动编制成员更加惯用。
  3. 但这不是经典 - 如果del关键字是真的&#34;坏&#34;它不会是语言的核心。我只是想扮演魔鬼的辩护者 - 解释为什么一些程序员可能会把它称为“坏”&#34;而且,可能会给你一个反对的立场。 ;)

答案 4 :(得分:2)

我认为我从来没有听过有人说del是邪恶的,至少没有其他任何语言特征。 del和其他方法之间的问题实际上归结为您的用例。以下情况适用于del

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

    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从范围中删除变量。这同样适用于字典中的值,或几乎所有通过名称或类似的不可变引用访问的内容(类属性,实例属性,dict值等)。

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

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

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

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

    这表明通过索引从列表中删除元素的基本问题:你几乎一次只能做一个。如果要删除两个元素的索引,那么删除第一个元素?您的旧索引很可能不会指向它曾经使用过的东西。列表用于存储订单。由于del会改变绝对顺序,因此您无法沿着列表行走或跳跃。同样,有坚实的用例(例如,随机破坏),但还有大量其他案例是错误的。特别是在新的Python程序员中,人们在函数上使用while循环做了可怕的事情(即循环,直到找到与输入匹配的值,del索引)。 Del需要索引作为输入,并且一旦运行,就会使引用该列表的所有现有索引引用完全不同的数据。如果维持多个索引,您可以看到维护噩梦的位置。再说一次,这还不错。只是在实践中很少用Python中的列表来做事情的最佳方式。

答案 5 :(得分:1)

关于“编辑”中的问题,

>>> 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 :(得分:1)

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
{}
>>>