When to use weak references in Python?

前端 未结 3 1918
迷失自我
迷失自我 2021-01-30 10:56

Can anyone explain usage of weak references?

The documentation doesn\'t explain it precisely, it just says that the GC can destroy the object linked to via a weak refere

3条回答
  •  野性不改
    2021-01-30 11:29

    Events are a common scenario for weak references.


    Problem

    Consider a pair of objects: Emitter and Receiver. The receiver has shorter lifetime than the emitter.

    You could try an implementation like this:

    class Emitter(object):
    
        def __init__(self):
            self.listeners = set()
    
        def emit(self):
            for listener in self.listeners:
                # Notify
                listener('hello')
    
    
    class Receiver(object):
    
        def __init__(self, emitter):
    
            emitter.listeners.add(self.callback)
    
        def callback(self, msg):
            print 'Message received:', msg
    
    
    e = Emitter()
    l = Receiver(e)
    e.emit() # Message received: hello
    

    However, in this case, the Emitter keeps a reference to a bound method callback that keeps a reference to the Receiver. So the Emitter keeps the Receiver alive:

    # ...continued...
    
    del l
    e.emit() # Message received: hello
    

    This is sometimes troublesome. Imagine that Emitter is a part of some data model that indicates when data changes and Receiver was created by a dialog window that listens to that changes to update some UI controls.

    Through the application's lifetime, multiple dialogs can be spawned and we don't want their receivers to be still registered inside the Emitter long after the window had been closed. That would be a memory leak.

    Removing the callbacks manually is one option (just as troublesome), using weak references is another.


    Solution

    There's a nice class WeakSet that looks like a normal set but stores its members using weak references and no longer stores them when they are freed.

    Excellent! Let's use it:

    def __init__(self):
        self.listeners = weakref.WeakSet()
    

    and run again:

    e = Emitter()
    l = Receiver(e)
    e.emit()
    del l
    e.emit()
    

    Oh, nothing happens at all! That's because the bound method (a specific receiver's callback) is orphaned now - neither the Emitter nor the Receiver hold a strong reference to it. Hence it's garbage collected immediately.

    Let's make the Receiver (not the Emitter this time) keep a strong reference to this callback:

    class Receiver(object):
    
        def __init__(self, emitter):
    
            # Create the bound method object
            cb = self.callback
    
            # Register it
            emitter.listeners.add(cb)
            # But also create an own strong reference to keep it alive
            self._callbacks = set([cb])
    

    Now we can observe the expected behaviour: the Emitter only keeps the callback as long as the Receiver lives.

    e = Emitter()
    l = Receiver(e)
    assert len(e.listeners) == 1
    
    del l
    import gc; gc.collect()
    assert len(e.listeners) == 0
    

    Under the hood

    Note that I've had to put a gc.collect() here to make sure that the receiver is really cleaned up immediately. It's needed here because now there's a cycle of strong references: the bound method refers to the receiver and vice versa.

    This isn't very bad; this only means that the receiver's cleanup will be deferred until the next garbage collector run. Cyclic references can't be cleaned up by the simple reference counting mechanism.

    If you really want, you could remove the strong reference cycle by replacing the bound method with a custom function object that would keep its self as a weak reference too.

    def __init__(self, emitter):
    
        # Create the bound method object
        weakself = weakref.ref(self)
        def cb(msg):
            self = weakself()
            self.callback(msg)
    
        # Register it
        emitter.listeners.add(cb)
        # But also create an own strong reference to keep it alive
        self._callbacks = set([cb])
    

    Let's put that logic into a helper function:

    def weak_bind(instancemethod):
    
        weakref_self = weakref.ref(instancemethod.im_self)
        func = instancemethod.im_func
    
        def callback(*args, **kwargs):
            self = weakref_self()
            bound = func.__get__(self)
            return bound(*args, **kwargs)
    
        return callback
    
    class Receiver(object):
    
        def __init__(self, emitter):
    
            cb = weak_bind(self.callback)
    
            # Register it
            emitter.listeners.add(cb)
            # But also create an own strong reference to keep it alive
            self._callbacks = set([cb])
    

    Now there's no cycle of strong references, so when Receiver is freed, the callback function will also be freed (and removed from the Emitter's WeakSet) immediately, without the need for a full GC cycle.

提交回复
热议问题