如何在Python中删除一个已实例化的对象?

53

我相对较新于面向对象编程,无法弄清如何在Python中删除一个已实例化的对象。

if self.hit_paddle(pos) == True or self.hit_paddle2(pos) == True:
    bar = bar + 1
if bar == 1:
    global barbox1
    barbox1 = barfill(canvas)
    barbox1.canvas.move(barbox1.id, 253, 367)
if bar == 2:
    global barbox2
    barbox2 = barfill(canvas)
    barbox2.canvas.move(barbox5.id, 413, 367)
    bar = 0
    time.sleep(0.2)
    barbox1 = None
    barbox2 = None

这就是代码了,我试图删除对象的主要方法是 barbox1 = None,但似乎没有起作用。


5
它为什么不能工作? - BrenBarn
你是否正在使用 pygame 或类似的东西?该库可能提供了一种从显示中删除对象的方法。将这些变量赋值为 None 可能会或可能不会起作用,因为你只是在删除一个引用,但该库可能有其他对这些对象的引用以使其保持活动状态。 - Bakuriu
1
你试图通过摆脱 barbox1 来实现什么目标? - Ethan Furman
据我理解,如果你将 barbox1 = None,由于 barbox1 是该对象唯一的引用,你已经删除了该对象并释放了该对象占用的内存,不需要担心变量名本身。 - Zhang Buzz
6个回答

71

object.__del__(self) 在实例即将被销毁时被调用。

>>> class Test:
...     def __del__(self):
...         print "deleted"
... 
>>> test = Test()
>>> del test
deleted

只有当对象的所有引用都被删除时,对象才会被删除(引自Ethan的说法)

此外,来自 Python 官方文档的引用:

del x 不会直接调用 x.__del__() ——前者将 x 的引用计数减少一,只有当 x 的引用计数达到零时,后者才会被调用。


4
我试过直接设置test = None__del__函数也会被调用。 - Zhang Buzz

29
你是指什么是“delete”吗?在Python中,使用“del”关键字可以删除引用或名称,但如果有其他名称指向同一对象,则该对象将不会被删除。
--> test = 3
--> print(test)
3
--> del test
--> print(test)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined

相比之下:

--> test = 5
--> other is test  # check that both name refer to the exact same object
True
--> del test       # gets rid of test, but the object is still referenced by other
--> print(other)
5

1
你是在暗示Python中没有可靠的删除对象的方法吗?例如,是否有可能获取到指向某个对象的所有引用并将它们全部删除? - Him
4
@Scott:如果你付出足够的努力,可能会找到一个对象的所有引用 - 但是,如果其中任何引用在你无法访问的命名空间(例如函数局部变量)中,你将无法使用“del”删除它们。 - Ethan Furman
2
@Him 没错,没有办法。幸运的是,如果有一些代码引用了该对象并尝试访问它,你认为会发生什么?这将相当于 C、C++ 和类似语言中的悬空指针。通过不给程序员删除正在使用的对象的手段来防止此类错误。 - BlackJack

3

Python使用称为引用计数的概念来管理内存。

这是什么意思呢?

当对象被创建时,会创建一个专门的内存来存储该对象,并返回该内存的地址。我们通常把地址存储在变量中。

Python跟踪存储对象地址的变量数量,一旦所有变量被删除/更改,Python实际上会删除为该对象分配的内存。

假设我们有一个名为Treasure的类。

class Treasure:
    def __del__(self):
        print("Treasure is gone")

print("John creates a treasure and he know the location of it (count is 1)")
john = Treasure()

print("John shares the treasure location with Stark (count is 2)")
stark = john

print("Stark shares the treasure location with Chris (count is 3)")
chris = stark

print("John dies (count is 2)")
del john

print("Stark dies (count is 1)")
del stark

print("Chris dies (count is 0)")
del chris

输出

John creates a treasure and he know the location of it (count is 1)
John shares the treasure location with Stark (count is 2)
Stark shares the treasure location with Chris (count is 3)
John dies (count is 2)
Stark dies (count is 1)
Chris dies (count is 0)
Treasure is gone

当Chris去世后,"宝藏已被掠夺"只会打印一次。这是因为当John去世时,Stark和Chris知道宝藏的位置;当Stark去世时,Chris知道宝藏的位置;但是当Chris去世时,没有人知道宝藏的位置。

只有在最后一个引用被删除时才会调用__del__方法。

您可以使用sys中的getrefcount来获取对象的当前引用数。示例:

import sys

class A:
    """
    test class
    """

a = A()
b = a

print(sys.getrefcount(a))

输出

3

通常由于在 getrefcount 调用中的临时引用,计数将增加一个。

1
在Python中,对象是通过存储指向该对象的内存地址来创建的。例如:
class SomeObject():
  def __init__(self, something, something_else):
    self.something = something
    self.something_else = something_else
  def some_method(self):
    return str(self.something)

a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
print(id(a) == id(a_variable_holding_a_memory_address))

输出:

True

如果你不知道,id()会返回Python变量的内存地址。 因此,如果你删除a_variable_holding_a_memory_address
class SomeObject():
  def __init__(self, something, something_else):
    self.something = something
    self.something_else = something_else
  def some_method(self):
    return str(self.something)

a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address
print(a_variable_holding_a_memory_address)
del a_variable_holding_a_memory_address
print(a)

输出:

<__main__.SomeObject object at 0x7f80787ea940>
<__main__.SomeObject object at 0x7f80787ea940>

发生的只是一个变量保存了一个对象(或引用)的内存地址被删除了,但是地址本身并没有被删除,因为还存在另一个对它的引用。 现在看看这个:

# In this example I use ctypes to print out the value at a memory address

import ctypes

class SomeObject():
  def __init__(self, something, something_else):
    self.something = something
    self.something_else = something_else
  def some_method(self):
    return str(self.something)

a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address

# print out the value at the memory address that a_variable_holding_a_memory_address holds
object_id = id(a_variable_holding_a_memory_address)
print(ctypes.cast(object_id, ctypes.py_object).value)

del a_variable_holding_a_memory_address

# now lets try it when only one of the references is deleted
print(ctypes.cast(object_id, ctypes.py_object).value)

del a

# the same thing but when both of the references are deleted
print(ctypes.cast(object_id, ctypes.py_object).value)

输出:

<__main__.SomeObject object at 0x7fbd04b5d3a0>
<__main__.SomeObject object at 0x7fbd04b5d3a0>
<__main__.SomeObject object at 0x7fbd04b5d3a0>
zsh: segmentation fault  python file.py

这个分段错误是因为程序尝试引用一个空内存地址。

使用Python语言相对于C语言的优点之一是它可以防止这种内存错误,除非你真的想让程序崩溃,就像在这个例子中一样。这包括自动垃圾回收(Python解释器的一部分,通过清空未使用的内存地址来释放内存)。

但是你看,当两个引用都存在时,内存地址保持一个对象。 当一个被删除时,该地址不会被删除,因为仍然存在一个引用该地址的引用。这就是自动垃圾收集器的工作原理。 当两者都被删除时,地址也被删除,因此出现了错误。还应注意的是,仅仅将变量赋予不同的值,如下所示:

a = "I now hold a str object"

改为:

del a

这也是可行的,因为对象的引用被替换为对另一个对象的引用,因此少了一个引用。

这不仅适用于用户定义的对象,还适用于Python中附带的非常基本的对象,通常被认为是理所当然的,例如strintlistdict等等。最终,在Python中变量所持有的就是一个对象。除非它持有像TrueFalseNone这样的布尔值。但是这些也是对象。Python文档中关于None的说明如下:

一个经常用来表示缺少值的对象,例如当默认参数没有传递给函数时。对None的赋值是非法的并会引发SyntaxError。None是NoneType类型的唯一实例。

引用计数:

在Python中,对象的引用计数是持有对该对象引用的变量数量。当该计数达到零时,将调用对象的__del__方法并销毁它(除非__del__按照文档中概述的执行以下操作:

虽然不建议这样做,但是del()方法可以通过创建对实例的新引用来推迟实例的销毁。这被称为对象复活。当一个复活的对象即将被销毁时,是否会再次调用del()取决于具体实现;当前的CPython实现只会调用一次。

如果您想要一个不会保持对象存活的对象引用,则有一种称为weakref的引用类型:

import weakref

class SomeObject():
  def __init__(self, something, something_else):
    self.something = something
    self.something_else = something_else
  def some_method(self):
    return str(self.something)

reference = SomeObject('something', 'something_else')

print(reference)

weak_reference = weakref.ref(reference)

print(weak_reference)

del reference

print(weak_reference)

输出:

<__main__.SomeObject object at 0x7f305b017880>
<weakref at 0x7f305ae7c630; to 'SomeObject' at 0x7f305b017880>
<weakref at 0x7f305ae7c630; dead>

weakrefweakreference 变成了一个指向空的对象,即一个死亡对象。

回到垃圾回收,通过使用内置库 gc,您可以更改 Python 垃圾回收器释放内存的一些方式。

其中之一是禁用垃圾回收:

import gc
# once again using ctypes
import ctypes
# disabling automatic garbage collection
gc.disable()

class SomeObject():
  def __init__(self, something, something_else):
    self.something = something
    self.something_else = something_else
  def some_method(self):
    return str(self.something)

a_variable_holding_a_memory_address = SomeObject(something=94, something_else=243)
a = a_variable_holding_a_memory_address

object_id = id(a_variable_holding_a_memory_address)
print(ctypes.cast(object_id, ctypes.py_object).value)

del a_variable_holding_a_memory_address

print(ctypes.cast(object_id, ctypes.py_object).value)

del a

print(ctypes.cast(object_id, ctypes.py_object).value)

输出:

<__main__.SomeObject object at 0x7f305ae6df10>
<__main__.SomeObject object at 0x7f305ae6df10>
<_ast.Interactive object at 0x7f305ae6df10>

即使没有对它的引用,该对象仍然存在。通常情况下,自动垃圾回收器会释放用于存储对象的内存空间,但我使用gc.disable()禁用了它。

_ast.Interactive对象是什么? 首先,这里是Python如何由其编写的C代码解释的简单概述:

  1. 将代码解析为标记数组
  2. 使用标记数组创建抽象语法树(ast,即_ast)。
  3. 从抽象语法树生成字节码
  4. 运行字节码

因此,在删除所有对对象的引用并禁用自动垃圾回收后,剩下的只是比字节码高一级的对象代码。


1

我不是完全清楚问题的目的,因为“那似乎行不通”并没有真正描述问题。也许存在一些关于删除对象或删除变量(即名称)之间差异的混淆。

无论如何,在所有当前其他答案中尚未提到的是,使用del并不是删除对象所必需的。 实际上,用户对对象的删除没有(直接)控制权。这由垃圾收集器在一定时间内完成,当没有引用该对象时或解释器关闭时。

有两种方法可以消除对对象的引用:

  1. Using del to remove the binding of a name from the local or global namespace:
    del barbox1
    
  2. Assigning another value to a name:
    barbox1 = None
    
在第一种情况下,名称(“变量”)barbox1被完全删除。在删除后尝试访问它将导致NameError,就像它从未被分配一样。
在第二种情况下,变量仍然存在,但不再引用该对象
在这两种情况下,如果没有其他对对象的引用,则垃圾收集器将删除对象(正如大多数其他答案中已经指出的那样)。
注意:除了可以用于演示对象何时被删除之外,__del__()方法对于删除对象并不真正相关,尽管没有硬性保证调用方法的时间或是否调用方法。它甚至可以用于避免对象的删除。但是,它肯定不会删除对象。

-3
简单回答: del <variable> 最小可重现示例
# Create variable
num = 5
num
# 5

# Delete it
del num

# Confirm it's gone
num
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# NameError: name 'num' is not defined

2
这只是删除引用(即名称);如果其他名称绑定到同一对象,则该对象仍然存在。 - Ethan Furman
del <variable> 通过删除内存中对象的指针来从环境中删除对象(即与更一般的删除方式相同,例如当某人右键单击计算机上的文件并选择“删除”时,它会删除指针但通常不会更改所写的内容)。可以使用垃圾回收来释放未使用的内存。要清除具有多个指针的内存。我将删除任何不再需要的变量,并让语言/垃圾回收来处理内存释放。 - stevec
1
您是错误的。如果环境中的其他名称仍然指向该名称,则对象不会从环境中删除 - 如果是这样,OP将不会遇到他们描述的问题。 - Ethan Furman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接