web-dev-qa-db-ja.com

クラスインスタンスを追跡する方法は?

プログラムの終わりに向かって、クラスのすべてのインスタンスから特定の変数を辞書にロードしようとしています。

例えば:

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

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

インスタンスの数が変化し、Foo()の各インスタンスからのxdictを新しいdictにロードしたいとします。どうすればいいですか?

SOで見た例では、インスタンスのリストがすでにあると想定しています。

20
dwstein

インスタンスを追跡する1つの方法は、クラス変数を使用することです。

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}

リストは1つだけです。

>>> 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    
32
Joel Cornett

@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が提示したより単純な実装で考えられる2つの問題に対処します。

  1. baseの各サブクラスは、独自のインスタンスを個別に追跡します。親クラスのインスタンスリストにサブクラスインスタンスを取得することはなく、1つのサブクラスが兄弟サブクラスのインスタンスに遭遇することはありません。ユースケースによっては、これは望ましくない場合がありますが、セットを分割するよりも、セットをマージして戻す方が簡単です。

  2. 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)
[]
25
Blckknght

インスタンスへの弱い参照を使用することをお勧めします。そうしないと、クラスは削除されたはずのインスタンスを追跡してしまう可能性があります。 weakref.WeakSetは、デッドインスタンスをセットから自動的に削除します。

インスタンスを追跡する1つの方法は、クラス変数を使用することです。

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}リストは1つだけです:

>>> 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>]   
8
OrenR61

metaclass を使用してこの問題を解決することもできます。

  1. クラスが作成されたとき(__init__メタクラスのメソッド)、新しいインスタンスレジストリを追加します
  2. このクラスの新しいインスタンスが作成されたとき(__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()
2
sfinkens

低レベルのハッキングとデバッグをすばやく行うためのもう1つのオプションは、gc.get_objects()によって返されるオブジェクトのリストをフィルタリングし、その方法で辞書をその場で生成することです。 CPythonでは、その関数はガベージコレクターが知っているすべての(一般的に巨大な)リストを返すので、特定のユーザー定義クラスのすべてのインスタンスが確実に含まれます。

これはインタプリタの内部を少し掘り下げているので、Jython、PyPy、IronPythonなどのようなもので動作する(または動作しない)ことに注意してください。私はチェックしていません。それにもかかわらず、それは本当に遅い可能性もあります。注意して/ YMMV /などを使用してください。

ただし、この質問に遭遇した人の中には、奇妙な動作をしているコードのスライスの実行時の状態で何が起こっているのかを理解するために、この種のことを1回限りで実行したいと思う人もいるかもしれません。この方法には、インスタンスやその構築にまったく影響を与えないという利点があります。これは、問題のコードがサードパーティのライブラリなどからのものである場合に役立ちます。

0
Walter Mundt

@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
0
dwstein