0

I have a class, 'Listener', that connects callbacks to D-Bus signals. The callbacks and signal names are provided by another class, 'Client'. If the callbacks provided by Client are passed to connect_to_signal (on dbus.Interface) as the callbacks to use when signals are received, everything works as expected, i.e. the callback method in class Client is called when the connected signal is received.

However, if I want to 'intercept' the signal and evaluate the payload before proceeding to call the Clients callback, I figured I could use a lambda expression and pass it to the connect_to_signal method, as in the example below:

import dbus
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from gi.repository import GObject


class Client(object):

    def __init__(self):
        bus = dbus.SystemBus()
        obj = bus.get_object("org.freedesktop.UDisks", "/org/freedesktop/UDisks")
        interface = dbus.Interface(obj, "org.freedesktop.UDisks")
        listener = Listener()
        signals_and_callbacks = {"DeviceAdded": self.device_added, 
            "DeviceChanged": self.device_changed}
        listener.listen_to_signals(interface, signals_and_callbacks)

    def device_added(self, payload):
        print "in device_added ", payload

    def device_changed(self, payload):
        print "in device_changed ", payload


class Listener(object):

    def listen_to_signals(self, interface, signals_and_callbacks):
        for signal, callback in signals_and_callbacks.items():
            cb = lambda x: self.signal_cb(x, callback)
            interface.connect_to_signal(signal, cb)

    def signal_cb(self, opath, subscriber_cb):
        print subscriber_cb
        subscriber_cb(opath)


if __name__ == "__main__":
    client = Client()
    mainloop = GObject.MainLoop()
    mainloop.run()

But this does not work as intended. The signals gets connected, in this case the code reacts to both 'DeviceAdded' and 'DeviceChanged', but only the last callback added gets called. If I only connect one signal the behaviour is as expected, but as soon as I connect more than one signal, passing lambda expressions as callbacks, both signals triggers the call to the last callback added.

Does anyone have any idea what's going on here?

JoGr
  • 1,457
  • 11
  • 22

1 Answers1

1

The basic problem is Python's scoping rules and how you set up the callback.
This example illustrates your problem (and the solution):

def test1():
    print("test1")

def test2():
    print("test2")

def caller(name, fn):
    print("calling function with name: {}".format(name))
    fn()

class Driver(object):
    def __init__(self):

        self.signals = []

    def connect_to_signal(self, name, what_to_call):
        self.signals.append((name, what_to_call))

    def run(self):
        for name, signal in self.signals:
            signal(1)



def main():
    signals = {'test1':test1, 'test2':test2}

    d = Driver()

    for signal, callback in signals.items():
        cb = lambda x: caller(signal, callback)
        #cb = lambda x,s=signal,c=callback: caller(s, c)  # TRY THIS INSTEAD!
        d.connect_to_signal(signal, cb)

    d.run()

if __name__ == '__main__':
    main()

If you run this function as-is, you get the following:

calling function with name: test2
test2
calling function with name: test2
test2

If you comment out the line starting cb = lambda x, and uncomment the following line, you now get the desired:

calling function with name: test1
test1
calling function with name: test2
test2

The reason is that you need to capture the variables in your lambda expression, or their values will just be what they are at the end of your loop.

Gerrat
  • 28,863
  • 9
  • 73
  • 101
  • Thanks! And here I was blaming the python-dbus lib for the n:th time and it turns out it was my fault again... Who would have thought? =D – JoGr Nov 20 '12 at 15:16