是坏的使用?
我通常在我的代码中使用del
来删除对象:
>>> array = [4, 6, 7, 'hello', 8] >>> del(array[array.index('hello')]) >>> array [4, 6, 7, 8] >>>
但是我听到很多人都说使用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' >>>
然而,使用它来“定义”对象有什么意义呢?
另外,为什么下面的代码更改这两个variables:
>>> 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 [] >>>
其他的答案是从技术的angular度来看待它(即修改列表最好的方法是什么),但是我会说人们build议的(更重要的)原因是切片,例如它不会修改原始的名单。
这个原因反过来就是这个名单通常来自某个地方。 如果你修改它,你可能会导致非常糟糕和难以察觉的副作用,这可能会导致程序中的其他地方出现bug。 或者即使您不立即引起错误,您也会使您的程序更难以理解和推理,并进行debugging。
例如,列表推导/生成器expression式是很好的,因为它们从不改变它们传递的“源”列表:
[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
这当然通常更昂贵(内存明智的),因为它创build了一个新的列表,但使用这种方法的程序在math上更纯粹并且更容易推理。 而懒惰列表(生成器和生成器expression式),甚至内存开销将消失,计算只能按需执行; 见http://www.dabeaz.com/generators/一个真棒介绍。; 在devise程序时,不要过多考虑优化(请参阅https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil )。 另外,从列表中删除一个项目是相当昂贵的,除非它是一个链表(Python的list
不是),链接列表请参阅collections.deque
)。
实际上,副作用自由函数和不可变 数据结构是function编程的基础, function编程是一个非常强大的编程范例。
但是,在某些情况下,可以修改数据结构(即使在FP中, 如果语言允许的话 ),比如当它是本地创build的数据结构,或者从函数的input中复制时:
def sorted(lst): ret = list(lst) # make a copy # mutate ret return ret
– 这个函数看起来是一个来自外部的纯函数,因为它不会修改它的input(也仅仅取决于它的参数,没有别的东西(即它没有(全局)状态),这是另一个要求的东西纯 function )。
所以只要你知道你在做什么, del
绝不是坏事; 但是要谨慎使用任何types的数据变化,只有在必须的时候。 始终从可能效率较低但更正确,math上优雅的代码开始。
…学习函数式编程 🙂
PS注意del
也可以用来删除局部variables,从而消除对内存中对象的引用,这通常对于任何GC相关的目的都是有用的。
回答你的第二个问题:
至于你关于del
完全移除对象的问题的第二部分 – 事实并非如此:事实上在Python中,甚至不可能告诉解释器/ VM从内存中移除一个对象,因为Python是一种垃圾回收语言(比如Java,C#,Ruby,Haskell等),它是运行时决定什么时候删除什么。
相反,当调用一个variables(而不是字典键或列表项)时, del
做这样的事情:
del a
它只是删除本地(或全局)variables,而不是variables指向的内容(Python中的每个variables都包含指向其内容的引用而不是内容本身)。 事实上,由于本地语言和全局语言都是作为一个字典存储的(参见locals()
和globals()
), del a
等价于:
del locals()['a']
或del globals()['a']
应用于全局。
所以如果你有:
a = [] b = a
你正在创build一个列表,在其中存储对它的引用,然后创build该引用的另一个副本,并将其存储到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 self.a
)时,你仍然在修改一个字典self.__dict__
,就像你实际上修改locals()
/ globals()
时一样。
PS作为Sven Marcnah指出, del locals()['a']
实际上并没有在函数内部删除局部variablesa
,这是正确的。 这可能是由于locals()
返回实际当地人的副本。 但是,答案依然普遍有效。
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
是不好的。 事实上,在某些情况下,它本质上是唯一合理的select,例如从字典中删除元素:
k = {'foo': 1, 'bar': 2} del k['foo']
也许问题是,初学者不完全理解variables如何在Python中工作,所以del
的使用(或误用)可能是陌生的。
del
本身的使用本身并不坏, 然而,它有两个方面,有助于特定的代码气味:
- 这是一个副作用,是一系列步骤的一部分,本身并没有意义。
- 这可能是
del
发生在手动内存pipe理的代码,表明对Python范围和自动内存pipe理的理解不够。 同样,with
语句比file.close
处理文件句柄更习惯file.close
,使用范围和上下文比手动nuking成员更习惯。
但是,这几乎是不可能的 – 如果del
关键字真的是“坏”的话,它就不会成为语言的核心。 我只是想扮演恶魔的倡导者 – 解释为什么一些程序员可能称之为“坏”,并可能给你一个反驳的立场。 ;)
我不认为我曾经听过有人说del
是邪恶的,至less没有任何其他的语言特征。 del
和其他方法之间的问题真的归结于你的用例。 以下情况对del
很有用:
-
从当前范围删除variables。 你为什么想做这个? 想象一下,你正在声明一个计算包variables的模块,但该模块的消费者从来不需要它。 虽然你可以为它创build一个全新的模块,但是这可能是矫枉过正或者可能模糊了实际计算的内容。 例如,您可能需要以下内容:
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
从范围中删除variables。 这同样适用于字典中的值,或几乎所有由名称或类似的不可变引用(类属性,实例属性,字典值等)访问的东西。 -
另一种情况是您想要从列表或类似的有序序列中删除项目的地方。 在某些方面,它们与第一种情况没有什么不同(因为它们都可以作为键值容器来访问,而列表恰好具有可靠sorting的整数键)。 在所有这些情况下,你都想要移除对该特定实例中存在的一些数据的引用(因为甚至类是一个类的实例)。 你正在做一个就地修改。
sorting和特殊索引是否意味着列表中的任何内容都不相同? 与列表的根本区别在于,进行就地修改会使所有旧密钥基本上无用,除非您非常小心。 Python为您提供了非常好的语义表示数据的能力:您可以使用
{'actor' : actor, 'verb' : verb, 'object' : object}
。 这种types的访问通常具有很多价值(这就是为什么我们按名称而不是数字来访问函数的原因):如果顺序不重要,为什么要使它变得僵化? 如果你的订单很重要,为什么你搞乱了一些东西使得所有对它的引用无效(即元素位置,元素之间的距离)。
问题归结为为什么你会直接通过索引删除列表值。 在大多数情况下,就地修改列表中的单个元素的操作通过其他函数具有明显的实现。 杀死一个给定值的项目? 你remove
它。 实现一个队列或堆栈? 你pop
它(不要locking它)。 减less列表中的实例的引用计数? l[i] = None
,你的旧指标仍指向相同的东西。 过滤元素? 你filter
或使用列表理解。 制作一个副本,减去一些元素? 你slice
。 摆脱重复,可散列的元素? 你可以list(set([]))
或者查看itertools
如果你只需要遍历唯一的元素一次。
在你摆脱所有这些情况之后,你最终会遇到两个常见的使用del
的列表。 首先,你可能会通过索引删除随机元素。 有很多情况下这可能是有用的, del
是完全合适的。 其次,你已经存储了表示你在列表中的位置的索引(例如,在走廊里从一个房间走到另一个房间,在这个走廊里你有时会从Charlie Sheen编程风格指南中随机地摧毁一个房间)。 如果你对同一个列表有多个索引,这会变得困难,因为使用del
意味着所有索引都需要相应地调整。 这是不太常见的,因为你使用索引走的结构往往不是你从元素中删除元素的元素(例如,游戏板的坐标网格)。 它确实发生,例如while循环遍历列表来轮询作业并删除已经完成的作业。
这表明根据索引就地从列表中删除元素的基本问题:您几乎一次只能做一个。 如果你有删除两个元素的索引,然后删除第一个? 您的旧索引很可能不会指向过去。 列表用于存储订单。 由于del
改变了绝对秩序,所以你被困在走路或跳跃列表中。 再次,有坚实的使用情况(例如,随机破坏),但也有很多其他情况是错误的。 特别是在新的Python程序员中,人们while
循环函数(循环,直到find与input相匹配的值, del
索引)之间做了很糟糕的事情。 Del
需要一个索引作为input,并且一旦运行,使所有引用该列表的现有索引指向完全不同的数据。 如果维护多个索引,您可以看到维护的恶梦。 再次,这并不坏。 只是在实践中很less用Python做一个列表。
关于你在“编辑”中的问题,
>>> 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中的内存由GCpipe理。 当调用一个对象的del
时,只需将它的引用计数减1(同时删除该作用域中的名称),当引用计数达到0时,该对象就被销毁。在这种情况下, b
仍然保存对该对象的引用,因此它不被破坏,仍然可以访问。
你可以在这里find更多的信息
del
只是改变了variables,这有时是不必要的。 因此,您的上述解决scheme可能会更好。 然而, del
是“摧毁”variables的唯一方法,并且永远删除它们:
>>> 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 {} >>>