Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Delegate Pattern - How to avoid circular reference?

I would to ask if using the Delegate Pattern in Python would lead to circular references and if so, what would be the best way to implement it to ensure the object and its delegate will be garbage collected?

In Objective C, the above problem is avoided by using a weak reference to the delegate. In C++, we don't call delete on the delegate. I've found a link to Python's weak reference module here: http://docs.python.org/library/weakref.html. It seems like a plausible approach might be to create a weak reference to refer to the instance variable using this module but I'm not sure.

As I've googled this question and was not able to find answers to it, I'm wondering whether this is even a problem in Python or if there is a common solution (without the need for the weakref module) that I'm unaware of? Also, I did search stackoverflow before asking but the questions I found either deal with circular imports or delegate pattern in general and not specific to Python and the problem of circular references.

Thanks in advance for any replies.

Listed below is some code for a toy example to help illustrate my question. I've implemented code in this way and it works but I'm not sure whether memory is garbage collected at the end.

class A(object):
    def __init__(self):
        self.delegate = None

        # Some other instance variables that keep track of state for performing some tasks.

    def doSomething(self):
        if self.delegate is not None:
            self.delegate.doSomething()
        else:
            print('Cannot perform task because delegate is not set.')

    # Other methods not shown.

class B(object):
    def __init__(self):
        self.a = A() # Need to keep object 'a' from garbage collected so as to preserve its state information.
        self.a.delegate = self  # Is this a circular reference? How to 'fix' it so that A and B will eventually be garbage collected?

    def doSomething(self):
        print('B doing something')

    # Other methods not shown.

EDIT:

After reading some of the replies, I decided to clarify my question. I understand that Python has garbage collection. What I wasn't sure was whether it will perform garbage collection on circular referenced objects. My worries stems from the following passage from Python's doc:

CPython implementation detail: CPython currently uses a reference-counting scheme with (optional) delayed detection of cyclically linked garbage, which collects most objects as soon as they become unreachable, but is not guaranteed to collect garbage containing circular references. See the documentation of the gc module for information on controlling the collection of cyclic garbage. Other implementations act differently and CPython may change. Do not depend on immediate finalization of objects when they become unreachable (ex: always close files).

The passage in its original form can be found here: http://docs.python.org/reference/datamodel.html The bold setting is mine.

The following post provides a clearer explanation on the problem of circular referenced objects and why it would prevent garbage collection on those objects (at least in a typical setting): http://www.electricmonk.nl/log/2008/07/07/python-destructor-and-garbage-collection-notes/.

Further, I just came across Alex Martellli's reply to the following question on whether Python users should worry about circular reference: Should I worry about circular references in Python? From his answer, I gather that even though circular referenced objects will eventually be garbage collected BUT there would be overheads. Whether it is significant depends on the program.

Further, he mentioned to use Python's weakref module but did not explicitly say how.

Hence, I would like to add the following questions to clarify some unresolved issues:

  1. The docs say garbaged collection is not guaranteed for circular referenced objects. But from the replies it appears that is not the case. So have I misunderstood the passage or are there further details that I've missed?
  2. I suppose using a weak reference, as stated in Alex's reply and my question, would avoid the overhead the problem entirely?

Again thanks for the replies.

like image 528
lightalchemist Avatar asked Oct 08 '22 19:10

lightalchemist


1 Answers

Python already does garbage collection. You only need to do something special if you write your own container types in C, as extensions.

Demo: Run this program and watch the memory usage not climb.

class C(object):
    pass

def circular():
    for x in range(10**4):
        for y in range(10**4):
            a = C()
            b = C()
            a.x = b
            b.x = a

circular()

Footnote: The following function doesn't do anything, delete it.

def setDelegate(self, delegate):
    self.delegate = delegate

Instead of calling x.setDelegate(y), you can use x.delegate = y. You can overload member access in Python, so there's no benefit to writing a method.

like image 72
Dietrich Epp Avatar answered Oct 10 '22 08:10

Dietrich Epp