在程序快结束时,我希望将类的所有实例中的特定变量加载到字典中。
例如:
class Foo():
__init__(self):
x = {}
foo1 = Foo()
foo2 = Foo()
foo...etc.
假设实例的数量会有所不同,我希望将 Foo() 的每个实例的 x dict 加载到新的 dict 中。我该怎么做?
我在 SO 中看到的示例假设一个已经有实例列表。
在程序快结束时,我希望将类的所有实例中的特定变量加载到字典中。
例如:
class Foo():
__init__(self):
x = {}
foo1 = Foo()
foo2 = Foo()
foo...etc.
假设实例的数量会有所不同,我希望将 Foo() 的每个实例的 x dict 加载到新的 dict 中。我该怎么做?
我在 SO 中看到的示例假设一个已经有实例列表。
跟踪实例的一种方法是使用类变量:
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
@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 提出的更简单实现的两个可能问题:
每个子类都base
将分别跟踪自己的实例。您不会在父类的实例列表中获得子类实例,并且一个子类永远不会偶然发现兄弟子类的实例。这可能是不可取的,具体取决于您的用例,但将这些集合重新合并在一起可能比将它们分开更容易。
该instances
集合使用对类实例的弱引用,因此如果您del
或将所有其他引用重新分配给代码中其他地方的实例,簿记代码将不会阻止它被垃圾收集。同样,这对于某些用例可能并不理想,但如果您真的希望每个实例永远存在,那么使用常规集合(或列表)而不是弱集很容易。
一些方便的测试输出(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)
[]
您可能希望对您的实例使用弱引用。否则,该类可能最终会跟踪本应删除的实例。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): instance.foo for instance in A.instances} 只有一个列表:
>>> 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 的回答),注册表是在创建第一个实例时添加的。
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()
快速低级黑客和调试的另一个选择是过滤返回的对象列表gc.get_objects()
并以这种方式动态生成字典。在 CPython 中,该函数将返回一个(通常是巨大的)垃圾收集器所知道的所有内容的列表,因此它肯定会包含任何特定用户定义类的所有实例。
请注意,这有点深入解释器的内部结构,因此它可能会也可能不会与 Jython、PyPy、IronPython 等类似的东西一起工作(或工作得很好)。我没有检查过。无论如何,它也可能真的很慢。谨慎使用/YMMV/等。
但是,我想有些遇到这个问题的人最终可能想要一次性做这种事情,以弄清楚某些行为异常的代码片段的运行时状态发生了什么。这种方法的好处是根本不影响实例或其构造,如果有问题的代码来自第三方库或其他东西,这可能很有用。
这是与 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()
将打印两个。
使用@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