如何跟踪类实例?

时间:2012-08-24 00:55:21

标签: python class dictionary self instances

在程序结束时,我希望将类的所有实例中的特定变量加载到字典中。

例如:

class Foo():
    __init__(self):
    x = {}

foo1 = Foo()
foo2 = Foo()
foo...etc.

假设实例的数量会有所不同,我希望每个Foo()实例的x dict加载到一个新的dict中。我该怎么做?

我在SO中看到的例子假设一个已经有实例列表。

7 个答案:

答案 0 :(得分:32)

跟踪实例的一种方法是使用类变量:

class A(object):
    instances = []

    def __init__(self, foo):
        self.foo = foo
        A.instances.append(self)

在程序结束时,您可以像这样创建dict:

foo_vars = {id(instance): instance.foo for instance in A.instances}

只有一个清单:

>>> a = A(1)
>>> b = A(2)
>>> A.instances
[<__main__.A object at 0x1004d44d0>, <__main__.A object at 0x1004d4510>]
>>> id(A.instances)
4299683456
>>> id(a.instances)
4299683456    
>>> id(b.instances)
4299683456    

答案 1 :(得分:25)

@ JoelCornett的答案完美地涵盖了基础知识。这是一个稍微复杂的版本,这可能有助于解决一些微妙的问题。

如果您希望能够访问给定类的所有“实时”实例,请将以下子类化(或在您自己的基类中包含等效代码):

from weakref import WeakSet

class base(object):
    def __new__(cls, *args, **kwargs):
        instance = object.__new__(cls, *args, **kwargs)
        if "instances" not in cls.__dict__:
            cls.instances = WeakSet()
        cls.instances.add(instance)
        return instance

这解决了@JoelCornett提出的更简单实现的两个可能问题:

  1. base的每个子类将分别跟踪自己的实例。您不会在父类的实例列表中获取子类实例,并且一个子类永远不会在兄弟子类的实例上发现。根据您的使用情况,这可能是不合需要的,但是将这些集合重新组合起来可能比将它们拆分开来更容易。

  2. instances集使用对类实例的弱引用,因此如果您del或重新分配代码中其他位置的所有其他引用,则簿记代码不会阻止它从垃圾收集。同样,对于某些用例来说,这可能并不理想,但如果您真的希望每个实例都能永久使用,那么使用常规集(或列表)而不是弱集很容易。

  3. 一些方便的测试输出(instances集总是传递给list只是因为它们不能很好地打印出来):

    >>> b = base()
    >>> list(base.instances)
    [<__main__.base object at 0x00000000026067F0>]
    >>> class foo(base):
    ...     pass
    ... 
    >>> f = foo()
    >>> list(foo.instances)
    [<__main__.foo object at 0x0000000002606898>]
    >>> list(base.instances)
    [<__main__.base object at 0x00000000026067F0>]
    >>> del f
    >>> list(foo.instances)
    []
    

答案 2 :(得分:8)

您可能希望对您的实例使用弱引用。否则,该类可能最终会跟踪要删除的实例。 weakref.WeakSet将自动从其集合中删除任何死实例。

跟踪实例的一种方法是使用类变量:

import weakref
class A(object):
    instances = weakref.WeakSet()

    def __init__(self, foo):
        self.foo = foo
        A.instances.add(self)

    @classmethod
    def get_instances(cls):
        return list(A.instances) #Returns list of all current instances

在程序结束时,您可以像这样创建dict:

foo_vars = {id(instance):例如A.instances中的instance.foo} 只有一个清单:

>>> a = A(1)
>>> b = A(2)
>>> A.get_instances()
[<inst.A object at 0x100587290>, <inst.A object at 0x100587250>]
>>> id(A.instances)
4299861712
>>> id(a.instances)
4299861712
>>> id(b.instances)
4299861712
>>> a = A(3) #original a will be dereferenced and replaced with new instance
>>> A.get_instances()
[<inst.A object at 0x100587290>, <inst.A object at 0x1005872d0>]   

答案 3 :(得分:2)

您也可以使用metaclass

解决此问题
  1. 创建类(元类的__init__方法)时,添加新的实例注册表
  2. 当创建此类的新实例(元类的__call__方法)时,将其添加到实例注册表。
  3. 这种方法的优点是每个类都有一个注册表 - 即使没有实例存在。相反,当覆盖__new__时(如Blckknght's answer中所述),在创建第一个实例时会添加注册表。

    class MetaInstanceRegistry(type):
        """Metaclass providing an instance registry"""
    
        def __init__(cls, name, bases, attrs):
            # Create class
            super(MetaInstanceRegistry, cls).__init__(name, bases, attrs)
    
            # Initialize fresh instance storage
            cls._instances = weakref.WeakSet()
    
        def __call__(cls, *args, **kwargs):
            # Create instance (calls __init__ and __new__ methods)
            inst = super(MetaInstanceRegistry, cls).__call__(*args, **kwargs)
    
            # Store weak reference to instance. WeakSet will automatically remove
            # references to objects that have been garbage collected
            cls._instances.add(inst)
    
            return inst
    
        def _get_instances(cls, recursive=False):
            """Get all instances of this class in the registry. If recursive=True
            search subclasses recursively"""
            instances = list(cls._instances)
            if recursive:
                for Child in cls.__subclasses__():
                    instances += Child._get_instances(recursive=recursive)
    
            # Remove duplicates from multiple inheritance.
            return list(set(instances))
    

    用法:创建一个注册表并将其子类化。

    class Registry(object):
        __metaclass__ = MetaInstanceRegistry
    
    
    class Base(Registry):
        def __init__(self, x):
            self.x = x
    
    
    class A(Base):
        pass
    
    
    class B(Base):
        pass
    
    
    class C(B):
        pass
    
    
    a = A(x=1)
    a2 = A(2)
    b = B(x=3)
    c = C(4)
    
    for cls in [Base, A, B, C]:
        print cls.__name__
        print cls._get_instances()
        print cls._get_instances(recursive=True)
        print
    
    del c
    print C._get_instances()
    

    如果使用abc模块中的抽象基类,只需子类abc.ABCMeta以避免元类冲突:

    from abc import ABCMeta, abstractmethod
    
    
    class ABCMetaInstanceRegistry(MetaInstanceRegistry, ABCMeta):
        pass
    
    
    class ABCRegistry(object):
        __metaclass__ = ABCMetaInstanceRegistry
    
    
    class ABCBase(ABCRegistry):
        __metaclass__ = ABCMeta
    
        @abstractmethod
        def f(self):
            pass
    
    
    class E(ABCBase):
        def __init__(self, x):
            self.x = x
    
        def f(self):
            return self.x
    
    e = E(x=5)
    print E._get_instances()
    

答案 4 :(得分:0)

使用@Joel Cornett的回答我提出了以下内容,这似乎有效。即我能够累计对象变量。

import os

os.system("clear")

class Foo():
    instances = []
    def __init__(self):
        Foo.instances.append(self)
        self.x = 5

class Bar():
    def __init__(self):
        pass

    def testy(self):
        self.foo1 = Foo()
        self.foo2 = Foo()
        self.foo3 = Foo()

foo = Foo()
print Foo.instances
bar = Bar()
bar.testy()
print Foo.instances

x_tot = 0
for inst in Foo.instances:
    x_tot += inst.x
    print x_tot

输出:

[<__main__.Foo instance at 0x108e334d0>]
[<__main__.Foo instance at 0x108e334d0>, <__main__.Foo instance at 0x108e33560>, <__main__.Foo instance at 0x108e335a8>, <__main__.Foo instance at 0x108e335f0>]
5
10
15
20

答案 5 :(得分:0)

快速低级别黑客攻击和调试的另一个选择是过滤gc.get_objects()返回的对象列表,并以此方式生成字典。在CPython中,该函数将返回垃圾收集器所知道的所有的(通常是巨大的)列表,因此它肯定会包含任何特定用户定义类的所有实例。

请注意,这正在挖掘解释器的内部,因此它可能会或可能不会与Jython,PyPy,IronPython等工作(或运行良好)。我没有检查。无论如何,它也可能非常缓慢。谨慎使用/ YMMV / etc。

但是,我想有些人遇到这个问题可能最终会想要做一次性的事情来弄清楚正在发生什么样的代码片段的运行状态正在发生奇怪的行为。这种方法的好处是不会影响实例或它们的构造,如果有问题的代码来自第三方库或其他东西,这可能很有用。

答案 6 :(得分:0)

这与Blckknght的方法类似,该方法也适用于子类。以为这可能会引起人们的兴趣,如果有人到这里来。一个区别是,如果B是A的子类,并且b是B的实例,则b将同时出现在A.instances和B.instances中。正如Blckknght所说,这取决于用例。

from weakref import WeakSet


class RegisterInstancesMixin:
    instances = WeakSet()

    def __new__(cls, *args, **kargs):
        o = object.__new__(cls, *args, **kargs)
        cls._register_instance(o)
        return o

    @classmethod
    def print_instances(cls):
        for instance in cls.instances:
            print(instance)

    @classmethod
    def _register_instance(cls, instance):
        cls.instances.add(instance)
        for b in cls.__bases__:
            if issubclass(b, RegisterInstancesMixin):
                b._register_instance(instance)

    def __init_subclass__(cls):
        cls.instances = WeakSet()


class Animal(RegisterInstancesMixin):
    pass


class Mammal(Animal):
    pass


class Human(Mammal):
    pass


class Dog(Mammal):
    pass


alice = Human()
bob = Human()
cannelle = Dog()
Animal.print_instances()
Mammal.print_instances()
Human.print_instances()

Animal.print_instances()将打印三个对象,而Human.print_instances()将打印两个对象。