【问题标题】:In Python: How to remove an object from a list if it is only referenced in that list?在 Python 中:如果仅在该列表中引用该对象,如何从列表中删除该对象?
【发布时间】:2016-05-14 23:21:08
【问题描述】:

我想跟踪当前正在使用的某种类型的对象。例如:跟踪一个类的所有实例或由元类创建的所有类。

这样跟踪实例很容易:

class A():
    instances = []
    def __init__(self):
        self.instances.append(self)

但是,如果在该列表之外的任何地方都没有引用某个实例,则将不再需要该实例,并且我不想在潜在的耗时循环中处理该实例。

我尝试使用 sys.getrefcount 删除仅在列表中引用的对象。

for i in A.instances:
    if sys.getrefcount(i) <=3: # in the list, in the loop and in getrefcount
        # collect and remove after the loop

我遇到的问题是引用计数非常模糊。 打开一个新的 shell 并创建一个没有内容的虚拟类返回 5

sys.getrefcount(DummyClass)

另一个想法是复制对象,然后删除列表并检查哪些对象已被安排进行垃圾收集,并在最后一步删除这些对象。比如:

Copy = copy(A.instances)
del A.instances
A.instances = [i for i in Copy if not copy_of_i_is_in_GC(i)]

当引用计数变为0时,不必立即删除对象。我只是不想在不再使用的对象上浪费太多资源。

【问题讨论】:

标签: python list object reference garbage-collection


【解决方案1】:

这个答案与凯文的答案相同,但我正在开发一个带有弱引用的示例实现,并将其发布在这里。使用弱引用解决了一个对象被self.instance列表引用的问题,所以它永远不会被删除。

为对象创建弱引用的其中一件事是,您可以在对象被删除时包含回调。存在诸如程序退出时没有发生回调之类的问题……但这可能是您想要的。

import threading
import weakref

class A(object):
    instances = []
    lock = threading.RLock()

    @classmethod
    def _cleanup_ref(cls, ref):
        print('cleanup') # debug
        with cls.lock:
            try:
                cls.instances.remove(ref)
            except ValueError:
                pass

    def __init__(self):
        with self.lock:
            self.instances.append(weakref.ref(self, self._cleanup_ref))

# test
test = [A() for _ in range(3)]
for i in range(3,-1,-1):
    assert len(A.instances) == i
    if test:
        test.pop()

print("see if 3 are removed at exit")
test = [A() for _ in range(3)]

【讨论】:

  • 在 Python shell (3.5.1) 中,测试循环中的断言失败,因为当循环第二次到达断言时尚未调用清理回调。在 assert 语句之前或 test.pop() 修复它之后将任何内容打印到标准输出。因此,在“assert”之前放置“False”可以修复它,而放置“None”并不能修复它。
  • @uzumaki 很有趣。我用 Python 3.4 进行了测试。我很困惑为什么在 3.5 中没有发生回调。
【解决方案2】:

解决这个问题的标准方法是通过weak references。基本思想是你保留一个对象的弱引用列表而不是对象本身,并定期从列表中删除失效的弱引用。

对于字典和集合,还有一些更抽象的类型,例如weakref.WeakKeyDictionary(),当您想将弱引用放在更复杂的地方(例如字典的键)时,可以使用它们。这些类型不需要手动修剪。

【讨论】:

    【解决方案3】:

    试试gc.get_referrers(obj)The gc module Documentation

    len(gc.get_referrers(my_obj))
    

    【讨论】:

      【解决方案4】:

      感谢@Barmar 指出使用weakref。我们可以将它与__del__ 方法结合起来,实现一个类的自管理实例列表。因此,OP 帖子中的class A 可以扩展为:

      from weakref import ref
      class A():
          instances = []
          def __init__(self):
              self.instances.append(ref(self))
      
          @staticmethod
          def __del__():
            if A:
              A.instances = [i for i in A.instances if not i() is None]
      

      测试

      #python2.7
      print dict((len(A.instances), A()) for i in range(5)).keys() # 0,1,2,3,4
      print len(A.instances) # 0
      

      析构函数__del__ 可以声明为静态方法或对象绑定方法,如def __del__(self):,尽管没有记录。后者可以通过创建另一个对它的引用来阻止对象被破坏。这里我使用静态的,因为不需要另一个对死亡对象的引用。上面的代码在 Python 2.7 和 3.3 中都经过测试。

      weakref.ref 回调的行为类似于__del__,只是它绑定到“weakref”对象。因此,如果您使用相同的回调函数为同一个对象创建多个weakrefs,它将被调用的时间与weakrefs 的数量完全相同。

      【讨论】:

      • 请注意,__del__ 不应该是静态方法。另外not i() is None 最好写成i() is not None。此外,正如 tdelaney 所示,weakref.ref 已经提供了添加在对象被销毁时调用的回调的方法。
      • @Bakuriu :感谢您的评论,请查看更新的答案以讨论有关 __del__weakref.ref 的讨论
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-11
      • 2020-11-13
      • 1970-01-01
      • 2021-10-28
      • 2012-04-03
      • 2014-06-29
      相关资源
      最近更新 更多