I am trying to implement something that works on the principle below:
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()
But I get :
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'
I understand that my implementation is not correct because Thing
instance gets GCed and deleted from the WeakValueDictionary
.
Is there any way I could achieve something like this to prevent the circular reference between Container
and Thing
?
Edit : If I change the code above for the one below, would it solve the circular reference issue ?
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)
You do not need to worry about circular references. Python is fully capable of managing its own memory in this case. And will delete objects with circular references as and when necessary.
Your implemenation need only look like this:
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
A quick test to show you how things work:
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