0

I am experiencing some weird bug in a code using observables in list comprehensions. I built here the simplest MWE I could think of:

class DummyObservable:
    def __init__(self, value):
        self.value = value
        self.subscribers = set()
    def __repr__(self):
        return f"Dummy (id={id(self)}, value={self.value}, cbs={len(self.subscribers)})"
    def subscribe(self, callback):
        self.subscribers.add(callback)
        callback(self.value)
        return lambda: self.subscribers.remove(callback)
    def get(self):
        return self.value
    def update(self, value):
        self.value = value
        for callback in self.subscribers:
            callback(value)

It seems to work fine (check the observable id):

a = DummyObservable("foo")
b = DummyObservable("bar")
u = a.subscribe(lambda x: print(f"{id(a)}: {x}"))

#[Out:] 140257126530832: foo

a.update("fonzie")
#[Out:] 140257126530832: fonzie

a,b
#[Out:] (Dummy (id=140257126530832, value=fonzie, cbs=1), Dummy (id=140257126528096, value=bar, cbs=0))

Now, trying to subscribe inside a list comprehension:

[s.subscribe(lambda x: print(f"{id(s)}: {x}")) for s in [a,b]]
#[Out:]  
        140257126530832: fonzie
        140257126528096: bar

Seems ok, so far. The id of a and b are printed correctly, showing the subscription is ok. But... is it really? Look, this strange behavior:

a.update("foobar")
#[Out:]  
        140257126530832: foobar
        140257126528096: foobar

What is going on? The update on "a" is calling the callback of "b". See running MWE.

Fred Guth
  • 1,537
  • 1
  • 15
  • 27
  • Pretty sure this is the reason: https://stackoverflow.com/questions/3431676/creating-functions-or-lambdas-in-a-loop-or-comprehension – cadolphs Feb 22 '23 at 19:29
  • It's not calling the callback of `b`. It's calling its own callback, but that callback has a free variable, `s`. And the value of `s` is `b` (since that is the las value it took inside the listcomprehension loop). This is no different than `def foo(): print(x)` then `x = 1; foo(); x = 2; foo()` or consider: `for x in range(3): foo()` thenb do `foo()` again after the loop has finished. – juanpa.arrivillaga Feb 22 '23 at 19:37
  • `unsubscribers = [(lambda s=s: s.subscribe(lambda x: print(f"source {id(s)} updated to {x}")))(s) for s in [a,b]]` for posterity – Fred Guth Feb 22 '23 at 20:38

0 Answers0