0

コールバックを D-Bus 信号に接続する「Listener」というクラスがあります。コールバックとシグナル名は、別のクラス「クライアント」によって提供されます。Client によって提供されるコールバックが、シグナルを受信したときに使用するコールバックとして connect_to_signal (dbus.Interface 上) に渡される場合、すべてが期待どおりに機能します。つまり、接続されたシグナルが受信されると、クラス Client のコールバック メソッドが呼び出されます。

ただし、信号を「傍受」してペイロードを評価してから Clients コールバックを呼び出したい場合は、次の例のように、ラムダ式を使用して connect_to_signal メソッドに渡すことができると考えました。

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()

しかし、これは意図したとおりには機能しません。シグナルが接続されます。この場合、コードは「DeviceAdded」と「DeviceChanged」の両方に反応しますが、最後に追加されたコールバックのみが呼び出されます。シグナルを 1 つだけ接続すると、動作は期待どおりになりますが、ラムダ式をコールバックとして渡して複数のシグナルを接続するとすぐに、両方のシグナルが最後に追加されたコールバックへの呼び出しをトリガーします。

ここで何が起こっているのか誰にも分かりませんか?

4

1 に答える 1

1

基本的な問題は、Python のスコープ ルールとコールバックの設定方法です。
この例は、問題 (および解決策) を示しています。

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()

この関数をそのまま実行すると、次のようになります。

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

cb = lambda x で始まる行をコメント アウトし、次の行のコメントを解除すると、必要な結果が得られます。

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

その理由は、ラムダ式の変数が必要なためです。そうしないとcapture、それらの値がループの最後にあるものになります。

于 2012-11-20T15:05:49.513 に答える