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提供的简单实现中的两个可能问题:
一些方便的花哨的测试输出(由于
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>] |
您还可以使用元类来解决此问题:
这种方法的优点是每个类都有一个注册表——即使不存在实例。相反,当覆盖
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) del c print C._get_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 | 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() |
快速低级黑客和调试的另一个选择是过滤
请注意,这是对解释器内部的一点挖掘,因此它可能会或可能不会与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 |