web-dev-qa-db-ja.com

Python)でweakrefを適切に使用する方法とタイミング

クラスのインスタンスが相互に親<->子参照を持っているコードがいくつかあります。例:

_class Node(object):
  def __init__(self):
    self.parent = None
    self.children = {}
  def AddChild(self, name, child):
    child.parent = self
    self.children[name] = child

def Run():
  root, c1, c2 = Node(), Node(), Node()
  root.AddChild("first", c1)
  root.AddChild("second", c2)
Run()
_

Ithinkこれにより、root、_c1_、および_c2_が後で解放されないような循環参照が作成されますRun()が完了しましたね。では、どうすればそれらを解放することができますか? root.children.clear()や_self.parent = None_のようなことができると思いますが、いつそれを行うべきかわからない場合はどうなりますか?

これはweakrefモジュールを使用するのに適切な時期ですか?正確には、私は何を弱くしますか? parent属性? children属性?オブジェクト全体?上記のすべて? WeakKeyDictionaryとweakref.proxyについての話はありますが、この場合、それらをどのように使用する必要があるかはわかりません。

これはpython2.4にもあります(アップグレードできません)。

更新:例と要約

どのオブジェクトをweakref-ifyするかは、どのオブジェクトが他のオブジェクトなしで生きることができるか、およびどのオブジェクトが互いに依存しているかによって異なります。最も寿命の長いオブジェクトには、寿命の短いオブジェクトへのweakrefが含まれている必要があります。同様に、依存関係に対してweakrefを作成しないでください。そうすると、依存関係がまだ必要であっても、依存関係が黙って消えてしまう可能性があります。

たとえば、子rootを持つツリー構造kidsがありますが、なしで存在できる場合)子の場合、rootオブジェクトはそのkidsにweakrefsを使用する必要があります。これは、子オブジェクトが親オブジェクトの存在に依存している場合にも当てはまります。以下では、子オブジェクトは、その深さを計算するために親を必要とします。したがって、parentのstrong-refです。ただし、kids属性のメンバーはオプションであるため、循環参照を防ぐためにweakrefが使用されます。

_class Node:
  def __init__(self)
    self.parent = None
    self.kids = weakref.WeakValueDictionary()
  def GetDepth(self):
    root, depth = self, 0
    while root:
      depth += 1
      root = root.parent
    return depth
root = Node()
root.kids["one"] = Node()
root.kids["two"] = Node()
# do what you will with root or sub-trees of it.
_

関係を逆転させるために、以下のようなものがあります。ここで、Facadeクラスが機能するにはSubsystemインスタンスが必要なので、必要なサブシステムへのstrong-refを使用します。ただし、Subsystemsは、動作するためにFacadeを必要としません。 Subsystemsは、Facadesに互いのアクションについて通知する方法を提供するだけです。

_class Facade:
  def __init__(self, subsystem)
    self.subsystem = subsystem
    subsystem.Register(self)

class Subsystem:
  def __init__(self):
    self.notify = []
  def Register(self, who):
    self.notify.append(weakref.proxy(who))

sub = Subsystem()
f1 = CliFacade(sub)
f2 = WebFacade(sub)
# Go on to reading from POST, stdin, etc
_
49

うん、weakrefはここで素晴らしい。具体的には、次の代わりに:

self.children = {}

使用する:

self.children = weakref.WeakValueDictionary()

コードを変更する必要は他にありません。このように、子に他の違いがない場合、子は消えるだけです。その子を値として持つ親のchildrenマップのエントリも消えます。

参照ループを回避することは、weakrefモジュールを使用する動機としてキャッシュを実装することと同等です。 Refループはあなたを殺すことはありませんが、特にあなたの記憶を詰まらせる可能性があります。インスタンスがそれらに関与しているクラスのいくつかが__del__を定義している場合、それはそれらのループを解消するgcのモジュール機能を妨げるためです。

30
Alex Martelli

child.parent = weakref.proxy(self)を使用することをお勧めします。これは、(外部参照)parentの有効期間がchildの有効期間をカバーする場合に循環参照を回避するための優れたソリューションです。逆に、weakrefの有効期間がchildの有効期間をカバーする場合は、(Alexが提案したように)childparentを使用します。ただし、weakrefparentの両方が他なしで存続できる場合は、childを使用しないでください。

ここでは、これらのルールを例とともに示します。 rootを変数に格納して渡し、子にアクセスする場合は、weakref-ed親を使用します。

def Run():
  root, c1, c2 = Node(), Node(), Node()
  root.AddChild("first", c1)
  root.AddChild("second", c2)
  return root # Note that only root refers to c1 and c2 after return, 
              # so this references should be strong

Rootにアクセスするときに、すべての子を変数にバインドする場合は、weakref-edの子を使用します。

def Run():
  root, c1, c2 = Node(), Node(), Node()
  root.AddChild("first", c1)
  root.AddChild("second", c2)
  return c1, c2

ただし、どちらも次の場合には機能しません。

def Run():
  root, c1, c2 = Node(), Node(), Node()
  root.AddChild("first", c1)
  root.AddChild("second", c2)
  return c1
18
Denis Otkidach

どの参照が弱い可能性があるかを明確にしたかったのです。次のアプローチは一般的ですが、すべての例で二重にリンクされたツリーを使用します。

論理ステップ1。

必要な限りすべてのオブジェクトを存続させるには、強力な参照があることを確認する必要があります。これは、次のようなさまざまな方法で実行できます。

  • [直接名]:ツリー内の各ノードへの名前付き参照
  • [コンテナ]:すべてのノードを格納するコンテナへの参照
  • [ルート+子]:ルートノードへの参照、および各ノードからその子への参照
  • [葉+親]:すべての葉ノードへの参照、および各ノードからその親への参照

論理ステップ2。

次に、必要に応じて、情報を表す参照を追加します。

たとえば、手順1で[コンテナ]アプローチを使用した場合でも、エッジを表す必要があります。ノードAとノードBの間のエッジは、単一の参照で表すことができます。それはどちらの方向にも行くことができます。繰り返しますが、たとえば次のような多くのオプションがあります。

  • [子]:各ノードからその子への参照
  • [親]:各ノードからその親への参照
  • [セットのセット]:2要素セットを含むセット。各2要素には、1つのエッジのノードへの参照が含まれています

もちろん、ステップ1で[root + children]アプローチを使用した場合、すべての情報はすでに完全に表現されているため、このステップはスキップします。

論理ステップ3。

次に、必要に応じて、パフォーマンスを向上させるための参照を追加します。

たとえば、ステップ1で[container]アプローチを使用し、ステップ2で[children]アプローチを使用した場合、特定のアルゴリズムの速度を向上させ、各ノードとその親の間に参照を追加したい場合があります。このような情報は、(パフォーマンスを犠牲にして)既存のデータから取得できるため、論理的に冗長です。


ステップ1のすべての参照は強力である必要があります

ステップ2と3のすべての参照は弱いか強いかもしれません。強力な参照を使用する利点はありません。サイクルが不可能であることがわかるまで、弱参照を使用することには利点があります。厳密に言えば、サイクルが不可能であることがわかったら、弱い参照を使用するか強い参照を使用するかは関係ありません。ただし、それについて考えるのを避けるために、ステップ2と3では弱い参照のみを使用することをお勧めします。

1
max