Skip to main content
 首页 » 编程设计

python之如何手动调用 dbus.service.signal 装饰器

2025年05月04日8JeffreyZhao

我正在尝试使用 dbus-python 将信号动态添加到 D-Bus 服务。它为此提供了一个装饰器,如果信号名称在模块加载时已知,则可以正常工作;但是,直到运行时我才知道导出到 D-Bus 的名称。

为了说明这个问题,我喜欢做的是道德上等价于:

import dbus 
import dbus.service 
import gobject 
from dbus.mainloop.glib import DBusGMainLoop 
 
class Event(dbus.service.Object): 
    def __init__(self, name): 
        self.name = name 
        self.busName = dbus.service.BusName('com.acme.EventManager', 
                                            bus=dbus.SessionBus()) 
        dbus.service.Object.__init__(self, 
                                     self.busName, 
                                     '/com/acme/EventManager/' + 
                                     self.name) 
 
        self.signame = 'com.acme.EventManager.' + self.name 
 
    # THIS DOES NOT WORK: this decorator is parsed before the Event 
    # class, and 'self' wouldn't exist here, anyway... 
    @dbus.service.signal(dbus_interface=self.signame, signature='v') 
    def emit(self, data): 
        print "In %s event, got: %s " % (self.name, data) 
 
if __name__ == "__main__": 
    DBusGMainLoop(set_as_default=True) 
    bus = dbus.SessionBus() 
    loop = gobject.MainLoop() 
    connect = Event('Connect') 
    disconnect = Event('Disconnect') 
    loop.run() 

毫不奇怪,这会产生:

@dbus.service.signal(dbus_interface=self.signame, signature='v') 
NameError: name 'self' is not defined 

我想我可以省去 @ 装饰运算符提供的语法糖,并在定义类后手动修补 Event,如下所示:

import dbus 
import dbus.service 
import gobject 
from dbus.mainloop.glib import DBusGMainLoop 
 
class Event(dbus.service.Object): 
    def __init__(self, name): 
        self.name = name 
        self.busName = dbus.service.BusName('com.acme.EventManager', 
                                            bus=dbus.SessionBus()) 
        dbus.service.Object.__init__(self, 
                                     self.busName, 
                                     '/com/acme/EventManager/' + 
                                     self.name) 
 
        self.signame = 'com.acme.EventManager.' + self.name 
 
    def emit(self, data): 
        print "In %s event, got: %s " % (self.name, data) 
 
if __name__ == "__main__": 
    DBusGMainLoop(set_as_default=True) 
    bus = dbus.SessionBus() 
    loop = gobject.MainLoop() 
    e1 = Event('Connect') 
    e1.emit = dbus.service.signal(dbus_interface=e1.signame, 
                              signature='v')(e1.emit) 
    loop.run() 

这运行没有错误,但无法将信号导出到 D-Bus。当我运行 D-Feet 时,我看到对象路径 /com/acme/EventManager/Connect 但它除了 Introspect() 之外没有任何接口(interface)方法。

为了看看我是否可以了解正在发生的事情,我在调试器中检查了传递给 dbus.service.signal 装饰器的函数的值。对于这个典型的用例:

@dbus.service.signal(dbus_interface='com.acme.foo', signature='v') 
def emit(self, data): 
    pass 

传递给装饰器的函数(在变量 func 中)看起来像这样:

>>> func 
>>> <function emit at 0x99fbed4> 

但是当我手动调用装饰器函数时(如上面第二个示例中的 e1.emit = 赋值),我看到:

>>> func 
>>> <bound method Event.emit of <__main__.Event at /com/acme/EventManager/Connect at 0x9b3348c>> 

所以...看来,对于正常用例,dbus.service.signal 期望获得免费功能---不是 一个未绑定(bind)的函数,但就所有意图和目的而言,该函数看起来像是使用以下方法定义的:

def emit(): 
    pass 

这种行为让我非常困惑。我已经阅读了很多关于装饰器的教程,并且认为我已经很好地理解了它们,但是我已经被这个难住了几个小时。如果此装饰器希望使用“原始”函数调用,我如何转换对象方法以便我可以手动调用它?

来自 this question ,我看到 types.MethodType() 可用于将自由函数转换为绑定(bind)方法。但似乎我需要做相反的事情(?)

请您参考如下方法:

我认为做你想做的事情的一种方法是使用工厂函数——但是以下是未经测试的,因为我没有安装 D-Bus 模块。

问题的根源在于您试图在类定义时使用装饰器,而该装饰器需要在创建该类的实例之前不会提供的数据。一种解决方法是在函数内部定义类并使用 closures因此数据在需要时可用。请注意,工厂函数返回创建的类的实例,而不是类本身,尽管如果需要也可以。

import dbus 
import dbus.service 
import gobject 
from dbus.mainloop.glib import DBusGMainLoop 
 
def event_factory(event_name): 
    class Event(dbus.service.Object): 
        def __init__(self): 
            self.busName = dbus.service.BusName('com.acme.EventManager', 
                                                bus=dbus.SessionBus()) 
            dbus.service.Object.__init__(self, 
                                         self.busName, 
                                         '/com/acme/EventManager/'+event_name) 
 
        @dbus.service.signal(dbus_interface='com.acme.EventManager.'+event_name, 
                             signature='v') 
        def emit(self, data): 
            print "In %s event, got: %s " % (event_name, data) 
 
    return Event() # return an instance of the class 
 
if __name__ == "__main__": 
    DBusGMainLoop(set_as_default=True) 
    bus = dbus.SessionBus() 
    loop = gobject.MainLoop() 
    connect = event_factory('Connect') 
    disconnect = event_factory('Disconnect') 
    loop.run()