2016-05-05 13 views
1

Ich versuche, etwas zu implementieren, die unten auf dem Prinzip funktioniert:Erfordert der zirkulare Bezug zwischen zwei Objekten die Verwendung von weakref?

from weakref import WeakValueDictionary 

class Container(object): 
    def __init__(self): 
     self.dic = WeakValueDictionary({}) 
    def put_in(self, something): 
     self.dic[something] = Thing(self, something) 

class Thing(object): 
    def __init__(self, container, name): 
     self.container = container 
     self.name = name 

    def what_I_am(self): 
     print("I am a thing called {}".format(self.name)) 

pot = Container() 
pot.put_in('foo') 
pot.dic['foo'].what_I_am() 

Aber ich bekomme:

File "C:/Users/jacques/ownCloud/dev/weakref.py", line 26, in <module> 
    pot.dic['foo'].what_I_am() 
    File "C:\Program Files\Anaconda3\lib\weakref.py", line 131, in __getitem__ 
    o = self.data[key]() 
KeyError: 'foo' 

Ich verstehe, dass meine Implementierung nicht korrekt ist, weil Thing Instanz GCed bekommt und aus der gelöscht WeakValueDictionary.

Gibt es eine Möglichkeit, etwas zu erreichen, um den Zirkelbezug zwischen Container und zu vermeiden?

Edit: Wenn ich den obigen Code für den folgenden ändern, würde es das zirkuläre Referenzproblem lösen?

from weakref import proxy 

class Container(dict): 
    def put_in(self, something): 
     self[something] = Thing(self) 

class Thing(object): 
    def __init__(self, container): 
     self.container = proxy(container) 

    def what_is_it(self): 
     print("I am a thing called {}".format(self)) 

    def __getattr__(self, name): 
     try: #Look up the Thing instance first 
      return object.__getattribute__(self, name) 
     except AttributeError: #Try to find the attribute in container 
      return self.container.__getattribute__(name) 

    def __format__(self, spec): 
     (name,) = (key for key, val in self.container.items() if self == val) 
     return name 

pot = Container() 
pot.location = 'Living room' 
pot.put_in('foo') 
pot['foo'].what_is_it() 
print(pot['foo'].location) 
+0

Beachten Sie, dass 'Thing (Selbst-)' * vor * genannt 'Selbst .dic [ding] = 'wird ausgeführt. – kennytm

+0

Ja, das ist das Problem, das ich mit einem normalen Diktat habe. Es ist ein bisschen wie das Dick und das Ei. Aber ich kann es lösen mit 'dic [Sache] = None' –

+0

" Thing "Instanz kann GC" na ja, es ** ist ** Müll gesammelt werden. Daher der 'KeyError'. –

Antwort

1

Sie müssen sich keine Gedanken über zirkuläre Referenzen machen. Python ist in diesem Fall vollständig in der Lage, seinen eigenen Speicher zu verwalten. Und löscht Objekte mit Zirkelreferenzen wenn nötig.

Ihre Implemenation braucht nur so aussehen:

class Container(dict): 
    def put_in(self, something): 
     self[something] = Thing(self, something) 

class Thing: 
    def __init__(self, container, name): 
     self.container = container 
     self.name = name 

    def what_is_it(self): 
     assert self.container[self.name] is self, "Thing stored under wrong name" 
     print("I am a thing called {}".format(self.name)) 

    def __getattr__(self, name): 
     # By the time __getattr__ is called, normal attribute access on Thing has 
     # already failed. So, no need to check again. Go straight to checking the 
     # container 
     try: 
      return getattr(self.container, name) 
     except AttributeError: 
      # raise a fresh attribute error to make it clearer that the 
      # attribute was initially accessed on a Thing object 
      raise AttributeError("'Thing' object has no attribute {!r}".format(name)) from e 

Ein schneller Test zu zeigen, wie die Dinge funktionieren:

c = Container() 
c.put_in("test") 
c.value = 0 

# Attribute demonstration 
c["test"].what_is_it() 
t = c["test"] 
print("name:", t.name) # get a Thing attribute 
print("value:", t.value) # get a Container Attribute 
c.name = "another name" 
print("Thing name:" t.name) # gets Thing attrs in preference to Container attrs 

# Garbage collection demonstration 
import weakref 
import gc 

r = weakref.ref(c["test"]) 
del c, t 
# no non-weak references to t exist anymore 
print(r()) # but Thing object not deleted yet 
# collecting circular references is non-trivial so Python does this infrequently 

gc.collect() # force a collection 
print(r()) # Thing object has now been deleted 
+0

Danke für den Tipp über '__getattr__'! –

+0

Die Zeile 'außer AttributeError' verfehlt das' as e' Bit. –

+0

Das Bit 'as e' ist nicht erforderlich, da die Ausnahme nicht verwendet wird. Daher ist keine Bindung an eine lokale Variable erforderlich. – Dunes

2

Der sehr Punkt WeakValueDictionary ist, dass seine Schlüssel automatisch gelöscht werden, wenn die Objekte nicht mehr in Gebrauch sind.

Unmittelbar nach

self.dic[thing] = Thing(self) 

gibt es keinen Hinweis auf das Thing Objekt außerhalb der WeakValueDictionary mehr, so dass das Verhalten, das Sie sehen werden, ist richtig und erwartet.

Wenn Sie erwarten, dass der Schlüssel erreichbar ist, ersetzen Sie WeakValueDictionary durch den regulären dict. Stellen Sie alternativ sicher, dass ein Verweis auf das Objekt vorhanden ist, z. B. indem Sie es zurückgeben oder woanders referenzieren.

+0

@ KevinJ.Chase Danke, behoben. Ich wollte klarstellen, dass es nicht die 'Thing'-Klasse ist, aber es sieht so aus, als hätte ich versehentlich noch mehr Verwirrung verursacht. – phihag