关于python:如何跟踪类实例?

How to keep track of class instances?

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

例如:

1
2
3
4
5
6
7
class Foo():
    __init__(self):
    x = {}

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

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

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


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

1
2
3
4
5
6
class A(object):
    instances = []

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

在程序结束时,您可以这样创建听写:

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

只有一个列表:

1
2
3
4
5
6
7
8
9
10
>>> 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
2
3
4
5
6
7
8
9
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提供的简单实现中的两个可能问题:

  • base的每个子类将分别跟踪自己的实例。您不会在父类的实例列表中得到子类实例,并且一个子类永远不会绊倒兄弟类的实例。这可能是不可取的,这取决于您的用例,但是将集合重新合并起来可能比将它们分开要容易得多。

  • instances集使用对类实例的弱引用,因此,如果您del或将所有其他引用重新分配给代码中其他地方的实例,簿记代码将不会阻止它被垃圾收集。同样,对于某些用例来说,这可能并不理想,但是如果您真的希望每个实例都永远持续下去,那么使用规则集(或列表)而不是weakset就足够容易了。

  • 一些方便的花哨的测试输出(由于instances集总是传递给list集,只是因为它们打印不好):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>> 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)
    []


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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    在程序结束时,您可以这样创建听写:

    foo_vars=id(instance):instance.foo,例如a.instances_只有一个列表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> 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>]


    您还可以使用元类来解决此问题:

  • 创建类(元类的__init__方法)时,添加新的实例注册表
  • 创建此类的新实例(元类的__call__方法)时,将其添加到实例注册表中。
  • 这种方法的优点是每个类都有一个注册表——即使不存在实例。相反,当覆盖__new__时(如blckknght的答案中所示),在创建第一个实例时添加注册表。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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))

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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子类化,以避免元类冲突:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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()

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

    请注意,这是对解释器内部的一点挖掘,因此它可能会或可能不会与Jython、Pypy、Ironpython等工具一起工作(或工作得很好)。我没有检查过。不管怎样,它也可能非常慢。小心使用/ymmv/etc。

    不过,我想,有些人遇到这个问题,最终可能会想一次性完成这类工作,以了解某些行为异常的代码片的运行时状态是如何的。这种方法的好处是根本不影响实例或实例的构造,如果所讨论的代码来自第三方库或其他东西,那么这种方法可能很有用。


    使用@joel cornett的答案,我已经想出了下面的方法,这似乎有效。也就是说,我能够汇总对象变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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

    输出:

    1
    2
    3
    4
    5
    6
    [<__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