Python:使用gc模块在交互模式下出现不同的行为

3

我希望能够获取到某个类的所有现存对象实例的引用元组。我的解决方案如下:

import gc

def instances(theClass):
    instances = []
    gc.collect()
    for i in gc.get_referrers(theClass):
        if isinstance(i, theClass):
            instances.append(i)
    return tuple(instances)

如果在Python解释器提示符处输入上述代码,则可以执行以下操作:
>>> class MyClass(object):
>>>     pass

>>> c = MyClass()
>>> instances(MyClass)
(<__main__.MyClass object at 0x100c616d0>,)

太好了。但是在函数内部似乎gc.collect()并没有实际作用:

>>> del c
>>> instances(MyClass)
(<__main__.MyClass object at 0x100c616d0>,)

但是gc.collect()只能在函数外使用:
>>> del c
>>> gc.collect()
>>> instances(MyClass)
()

那么,我的问题是:当在函数内部时,如何使gc.collect()实际执行完整的收集(为什么原来的代码不起作用)?相关问题是:是否有更好的方法来实现相同目标,即返回特定类的对象实例的元组引用?
注:所有这些都是在Python 2.7.3中尝试的。我还没有在Python 3中尝试过,但我的目标是拥有一个可以在任何版本中工作的函数(或至少可以使用2to3进行转换)。
编辑(根据下面的答案)以澄清问题实际上是关于交互模式而不是gc.collect()函数本身。

也许与您的问题无关,但为什么要在get_referrers输出上调用enumerate?您从未使用索引做任何事情。 - user2357112
如果不是必要的话,那很可能是因为我不知道自己在做什么。我原以为需要从get_referrers中获取每个列表项,然后将其分配给i并在随后的行中使用。 - Ivan X
1
你可以直接迭代:for thing in gc.get_referrers(MyClass): if isinstance(thing, MyClass): ... - user2357112
我明白你的意思了,我编辑了问题以简化它。谢谢。 - Ivan X
2个回答

4
当您在交互模式下工作时,有一个内置的神奇变量_,它保存了您运行的最后一个表达式语句的结果:
>>> 3 + 4
7
>>> _
7

当您删除变量 c 时, del c 不是表达式,因此 _ 保持不变:

>>> c = MyClass()
>>> instances(MyClass)
(<__main__.MyClass object at 0x00000000022E1748>,)
>>> del c
>>> _
(<__main__.MyClass object at 0x00000000022E1748>,)

_保留了对MyClass实例的引用。当您调用gc.collect()时,它是一个表达式,因此gc.collect()的返回值替换了_的旧值,并且c最终被收集。这与垃圾回收器无关;任何表达式都可以:

>>> 4
4
>>> instances(MyClass)
()

非常有启发性的回答。谢谢。我不知道交互模式可能会产生不同的行为。我还没有尝试将其作为脚本运行,但是当我刚刚尝试时,我很高兴地看到它不仅按预期执行,而且似乎根本不需要gc.collect()。 - Ivan X
@IvanX:在CPython中,gc.collect()仅清理引用循环。如果您的程序中没有引用循环,那么什么也不会发生,这可能解释了为什么您在测试中不需要调用gc.collect() - Benjamin Hodgson

1
我认为有一种更简单、更可靠的方法可以获取所需信息,而无需在gc中翻找:您可以让类负责跟踪其实例。
在这里,我使用元类将每个InstanceTracker子类附加到实例列表上,并覆盖__new__以将每个创建的实例添加到列表中。(这是Python 3代码,需要稍作调整才能与Python 2一起使用。)
class InstanceTrackerMeta(type):
    def __new__(meta, name, bases, dct):
        cls = super().__new__(meta, name, bases, dct)
        cls.instances = []
        return cls

class InstanceTracker(metaclass=InstanceTrackerMeta):
    def __new__(cls, *args, **kwargs):
        instance = super().__new__(cls, *args, **kwargs)
        cls.instances.append(instance)
        return instance


# subclass InstanceTracker to get a class which remembers its instances
class MyClass(InstanceTracker):
    pass

c = MyClass()
print(MyClass.instances)
# [<__main__.MyClass object at 0x107b9d9b0>]

注意:根据您是否想跟踪子类的实例等情况,此代码可能需要进行调整。如果您希望在垃圾回收时删除实例,则需要在InstanceTracker中覆盖__del__

如果您只需要跟踪系统中某个类的实例,则还可以简化它以摆脱元类。


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