WeakMethod in WeakSet

45 Views Asked by At

I would like to use the functionality of a weakref.WeakSet, but in this set I would like to store bound methods, so I have to use weakref.WeakMethod.

Here's a stripped down example:

import weakref

class C:
    def method(self): pass

ci = C()

print("weakMethod:", weakref.WeakMethod(ci.method))
print("s1:", weakref.WeakSet([C.method]))
print("s2:", weakref.WeakSet([ci.method]))
print("s3:", weakref.WeakSet([weakref.WeakMethod(ci.method)]))

which gives me (with Python 3.12.2)

weakMethod: <weakref at 0x7f569e9308d0; to 'C' at 0x7f569e96dca0>
s1: {<weakref at 0x7f56ac12a520; to 'function' at 0x7f569e98ade0 (method)>}
s2: set()
s3: set()

As you can see in the first line, WeakMethod works as expected, but storing it in a WeakSet yields an empty s3.

Side note: s2 is empty as expected, but storing a weak reference to an unbound method as in s1 works.

Obvious workaround: Use a set instead of a WeakSet and replicate its functionality.

Question: Is there a more elegant way of combining the functionality of WeakSet and WeakMethod?

1

There are 1 best solutions below

0
Tom Pohl On

Here's the current "obvious" workaround I use in the context of an observer pattern:

class Observable:
    def __init__(self):
        self.observers = set()

    def observe(self, method):
        def remote_observer(observer):
            print(f"removed {observer} from observers")
            self.observers.remove(observer)

        self.observers.add(weakref.WeakMethod(method, remote_observer))

    def unobserve(self, func):
        self.observers.discard(func)

    def call_observers(self):
        for observer in self.observers:
            observer()()


observable = Observable()


class ACME:
    def __init__(self):
        for _ in range(5):
            observable.observe(self.observer)

    def observer(self):
        print("ACME observer called")

acme = ACME()
print("### expecting one observer to be called ###")
observable.call_observers()
del acme
print("### expecting no observer to be called ###")
observable.call_observers()

which outputs:

### expecting one observer to be called ###
ACME observer called
removed <weakref at 0x7f1240feee50; dead> from observers
### expecting no observer to be called ###

Notice that the set does a great job and boils the five equal observers down to a single observer and the WeakMethod removes to observer after del acme. If you replace the WeakMethod with a direct reference, it will keep acme and its observer alive despite the del acme (which is exactly what I try to avoid).

Ok, this works, but I'm curious if you have a more elegant solution using WeakSet.