我正在尝试使用 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()
