我通常在代码中使用del
来删除对象:
>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>>
但我听说many people说del
的使用是单声道的。使用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
[]
>>>
答案 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
本身并不坏;然而,它有两个方面导致特定代码气味:
del
出现在具有手动内存管理的代码中,表明对Python范围和自动内存管理的理解不足。与with
语句处理文件句柄比file.close
更惯用的方式相同,使用范围和上下文比手动编制成员更加惯用。但这不是经典 - 如果del
关键字是真的&#34;坏&#34;它不会是语言的核心。我只是想扮演魔鬼的辩护者 - 解释为什么一些程序员可能会把它称为“坏”&#34;而且,可能会给你一个反对的立场。 ;)
答案 4 :(得分:2)
我认为我从来没有听过有人说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
从范围中删除变量。这同样适用于字典中的值,或几乎所有通过名称或类似的不可变引用访问的内容(类属性,实例属性,dict值等)。
另一种情况是您要从列表或类似的有序序列中删除项目。在某些方面,它与第一种情况没有什么不同(因为它们都可以作为键值容器被访问,列表恰好具有可靠排序的整数键)。在所有这些情况下,您都希望删除对该特定实例中存在的某些数据的引用(因为即使类是一个类的实例)。你正在进行就地修改。
订购和特殊索引是否意味着列表有什么不同?与列表的根本区别在于,进行就地修改会使所有旧密钥基本无用,除非您非常小心。 Python为您提供了非常语义表示数据的强大功能:您可以拥有[actor, verb, object]
的优秀词典,而不是拥有{'actor' : actor, 'verb' : verb, 'object' : object}
列表和映射索引。在那种类型的访问中通常有很多价值(这就是我们通过名称而不是数字访问函数的原因):如果订单不重要,为什么要使它变得僵硬?如果您的订单很重要,为什么搞乱某些东西会使您对它的所有引用都无效(即元素位置,元素之间的距离)。
问题归结为您为什么要按索引直接删除列表值。在大多数情况下,就地修改列表的单个元素的操作通过其他函数具有明显的实现。杀死具有给定值的项目?你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
(a
和b
指向内存中的同一对象)并且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
{}
>>>