del 在 Python 中何时有用?
- 2024-12-25 08:50:00
- admin 原创
- 108
问题描述:
我真的想不出 Python 需要这个del
关键字的任何理由(而且大多数语言似乎没有类似的关键字)。例如,与其删除变量,不如直接给None
它赋值。而当从字典中删除时,del
可以添加一个方法。
是否有理由保留del
在 Python 中,或者它是 Python 垃圾收集前时代的遗迹?
解决方案 1:
首先,除了局部变量之外,你还可以删除其他东西
del list_item[4]
del dictionary["alpha"]
显然,这两种方法都很有用。其次,del
在局部变量上使用可以使意图更清晰。比较:
del foo
到
foo = None
我知道在这种情况下del foo
,目的是将变量从作用域中移除。不清楚是谁foo = None
在做这件事。如果有人只是分配,foo = None
我可能会认为这是死代码。但我立刻就知道编码的人del foo
想做什么。
解决方案 2:
这是其中的一部分del
(来自Python 语言参考):
删除名称会从本地或全局命名空间中删除该名称的绑定
分配None
给名称并不会从命名空间中删除名称的绑定。
(我认为关于删除名称绑定是否真的有用可能会有一些争论,但这是另一个问题。)
解决方案 3:
我发现del
有用的地方之一是清理 for 循环中的无关变量:
for x in some_list:
do(x)
del x
现在,您可以确定,如果在 for 循环之外使用 x,它将未定义。
解决方案 4:
删除变量与将其设置为 None 不同
使用 删除变量名del
可能很少使用,但如果不使用关键字,则无法轻松实现。如果你可以通过编写 来创建变量名a=1
,那么理论上可以通过删除 a 来撤消此操作,这很好。
在某些情况下它可以使调试更容易,因为尝试访问已删除的变量将引发 NameError。
您可以删除类实例属性
Python 允许你编写如下代码:
class A(object):
def set_a(self, a):
self.a=a
a=A()
a.set_a(3)
if hasattr(a, "a"):
print("Hallo")
如果你选择动态地向类实例添加属性,你肯定希望能够通过以下方式撤消它:
del a.a
解决方案 5:
有一个具体的例子说明何时应该使用它del
(可能还有其他例子,但我暂时知道这个例子),当你使用它sys.exc_info()
来检查异常时。此函数返回一个元组、引发的异常类型、消息和回溯。
前两个值通常足以诊断错误并采取行动,但第三个值包含引发异常和捕获异常之间的整个调用堆栈。特别是,如果你做了类似的事情
try:
do_evil()
except:
exc_type, exc_value, tb = sys.exc_info()
if something(exc_value):
raise
回溯tb
最终会进入调用堆栈的本地,从而创建一个无法被垃圾回收的循环引用。因此,重要的是:
try:
do_evil()
except:
exc_type, exc_value, tb = sys.exc_info()
del tb
if something(exc_value):
raise
打破循环引用。在很多情况下,当您想要调用时sys.exc_info()
,比如使用元类魔法,回溯很有用,因此您必须确保在离开异常处理程序之前清理它。如果您不需要回溯,您应该立即删除它,或者只需执行以下操作:
exc_type, exc_value = sys.exc_info()[:2]
一起避免这一切。
解决方案 6:
只是另一种想法。
在 Django 等框架中调试 http 应用程序时,调用堆栈中充满了之前使用过的无用且混乱的变量,尤其是当它是一个非常长的列表时,这对开发人员来说可能非常痛苦。所以,此时,命名空间控制可能会很有用。
解决方案 7:
明确使用“del”也比将变量赋值为 None 更好。如果您尝试删除不存在的变量,您将收到运行时错误,但如果您尝试将不存在的变量设置为 None,Python 会默默地将新变量设置为 None,而您想要删除的变量仍留在原处。因此 del 将帮助您尽早发现错误
解决方案 8:
del
经常出现在__init__.py
文件中。文件中定义的任何全局变量__init__.py
都会自动“导出”(它将包含在 中from module import *
)。避免这种情况的一种方法是定义__all__
,但这可能会变得混乱,而且并不是每个人都会使用它。
例如,如果你有如下__init__.py
代码
import sys
if sys.version_info < (3,):
print("Python 2 not supported")
然后你的模块会导出sys
名称。你应该改写
import sys
if sys.version_info < (3,):
print("Python 2 not supported")
del sys
解决方案 9:
我发现del
在使用 Numpy 处理大数据时,伪手动内存管理很有用。例如:
for image_name in large_image_set:
large_image = io.imread(image_name)
height, width, depth = large_image.shape
large_mask = np.all(large_image == <some_condition>)
# Clear memory, make space
del large_image; gc.collect()
large_processed_image = np.zeros((height, width, depth))
large_processed_image[large_mask] = (new_value)
io.imsave("processed_image.png", large_processed_image)
# Clear memory, make space
del large_mask, large_processed_image; gc.collect()
当 Python GC 无法跟上时,系统会疯狂地进行交换,从而导致脚本陷入停顿;而当内存阈值低于宽松时,脚本可以在宽松的内存阈值下完美流畅地运行,从而为机器在工作时浏览和编码留下足够的空间。
解决方案 10:
对以上答案补充几点:del x
的定义x
表示r -> o
(r
指向对象的引用o
)但del x
发生了变化,r
而不是o
。它是对对象的引用(指针)而不是与关联的对象的操作x
。区分r
和o
是这里的关键。
它将其从 中删除
locals()
。globals()
如果属于那里则将其移除x
。将其从堆栈框架中移除(从物理上移除引用,但对象本身驻留在对象池中而不是堆栈框架中)。
将其从当前范围中移除。限制局部变量的定义范围非常有用,否则可能会引起问题。
它更多的是名称的声明而不是内容的定义。
它影响的是
x
属于哪里,而不是x
指向哪里。内存中唯一的物理变化就是这个。例如,如果x
在字典或列表中,它(作为引用)将从那里移除(不一定从对象池中移除)。在这个例子中,它所属的字典是堆栈框架(locals()
),它与重叠globals()
。
解决方案 11:
使用 numpy.load 后强制关闭文件:
也许这只是一种小众用途,但我发现它在读取文件时很有用numpy.load
。偶尔我会更新文件,并需要将同名文件复制到目录中。
我曾经del
发布过该文件并允许我复制新文件。
请注意,我想避免使用with
上下文管理器,因为我正在命令行上处理图表,并且不想多次按下 Tab 键!
请参阅这个问题。
解决方案 12:
我想详细说明一下可以接受的答案,以强调设置变量与None
删除变量之间的细微差别del
:
给定变量foo = 'bar'
,以及以下函数定义:
def test_var(var):
if var:
print('variable tested true')
else:
print('variable tested false')
一旦最初宣布,test_var(foo)
收益variable tested true
将符合预期。
现在尝试:
foo = None
test_var(foo)
其结果是variable tested false
。
将此行为与以下行为进行对比:
del foo
test_var(foo)
现在提出了NameError: name 'foo' is not defined
。
解决方案 13:
作为其用途的一个例子del
,我发现它在如下情况下很有用:
def f(a, b, c=3):
return '{} {} {}'.format(a, b, c)
def g(**kwargs):
if 'c' in kwargs and kwargs['c'] is None:
del kwargs['c']
return f(**kwargs)
# g(a=1, b=2, c=None) === '1 2 3'
# g(a=1, b=2) === '1 2 3'
# g(a=1, b=2, c=4) === '1 2 4'
这两个函数可以位于不同的包/模块中,程序员不需要知道实际的默认值参数是什么。因此,通过将 kwargs 与 del 结合使用,您可以将其设置为 None(或c
在f
这种情况下保留它),表示“我想要 c 上的默认值”。
你可以用类似下面的方法做同样的事情:
def g(a, b, c=None):
kwargs = {'a': a,
'b': b}
if c is not None:
kwargs['c'] = c
return f(**kwargs)
然而我发现前面的例子更加DRY和优雅。
解决方案 14:
del 在 python 中何时有用?
您可以使用它来删除数组中的单个元素,而不是使用切片语法x[i:i+1]=[]
。例如,如果您在os.walk
目录中并希望删除元素,这可能会很有用。不过,我认为关键字对此没有用处,因为只需创建一个[].remove(index)
方法(该.remove
方法实际上是搜索并删除值的第一个实例)。
解决方案 15:
“del”命令对于控制数组中的数据非常有用,例如:
elements = ["A", "B", "C", "D"]
# Remove first element.
del elements[:1]
print(elements)
输出:
[‘B’,‘C’,‘D’]
解决方案 16:
我认为 del 有自己的语法的原因之一是,在某些情况下用函数替换它可能很难,因为它操作绑定或变量而不是它引用的值。因此,如果要创建 del 的函数版本,则需要传入上下文。del foo 需要变成 globals().remove('foo') 或 locals().remove('foo'),这会变得混乱且可读性较差。尽管如此,我认为摆脱 del 会很好,因为它似乎很少使用。但删除语言功能/缺陷可能会很痛苦。也许 python 4 会删除它 :)
解决方案 17:
del
删除变量和它指向的对象的绑定。
>>> a = ['a', 'b', 'c']
>>> b = a
>>> del a
>>> b
['a', 'b', 'c']
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
我能想到的一个简单用例是,如果您使用内置函数名作为变量,并且您想在它被变量名“覆盖”之后使用该函数。
t = ('a', "letter")
value, type = t
print(value, type)
del type
print(type(value))
输出:
a letter
<class 'str'>
解决方案 18:
另一个小众案例,但很有用。
from getpass import getpass
password = getpass()
token = get_auth_token(password)
del password
# Assume more code here...
删除变量后password
,您不会面临稍后错误地打印出该变量的风险,也不会面临最终出现在日志或堆栈跟踪中的风险。
解决方案 19:
del
将从当前作用域中删除变量,除非重新初始化。将其设置为None
可将其保留在当前作用域中。
a = "python string"
print(a)
del a
print(a)
a = "new python string"
print(a)
输出:
python string
Traceback (most recent call last):
File "testing.py", line 4, in <module>
print(a)
NameError: name 'a' is not defined
解决方案 20:
由于我还没有看到交互式控制台答案,因此我将展示一个。
当foo=None
该引用和对象存在时,它并没有指向它。
同时del foo
也破坏了对象和引用。
因此,如果你执行了这样的操作if foo is None
并且它被删除了,它将rise
NameError
作为引用,它的对象以及其间的所有内容都被删除了del
删除目标列表会从左到右递归删除每个目标。
同时foo=None
只是一个指向的引用,None
所以引用仍然有效,对象也一样。
[...]在 Python 中,变量是对对象的引用,任何变量都可以引用任何对象[...]
引文 1 链接
引文2链接
解决方案 21:
还有另一种特殊用法:在具有 ROOT5 或 ROOT6 的pyroot中,“del”可能有助于删除引用不再存在的 C++ 对象的 python 对象。这允许 pyroot 的动态查找找到同名的 C++ 对象并将其绑定到 python 名称。因此,您可以有这样的场景:
import ROOT as R
input_file = R.TFile('inputs/___my_file_name___.root')
tree = input_file.Get('r')
tree.Draw('hy>>hh(10,0,5)')
R.gPad.Close()
R.hy # shows that hy is still available. It can even be redrawn at this stage.
tree.Draw('hy>>hh(3,0,3)') # overwrites the C++ object in ROOT's namespace
R.hy # shows that R.hy is None, since the C++ object it pointed to is gone
del R.hy
R.hy # now finds the new C++ object
希望 ROOT7 能够通过更合理的对象管理来解决这个难题。
解决方案 22:
有一次我不得不使用:
del serial
serial = None
因为仅使用:
serial = None
没有足够快地释放串行端口以立即再次打开它。从那节课中我了解到这del
实际上意味着:“立即 GC 它!并等到它完成”,这在很多情况下确实很有用。当然,你可能有一个system.gc.del_this_and_wait_balbalbalba(obj)
。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)