1
# -*- coding: utf-8 -*-
3
Created on Mon Mar 11 18:39:13 2013
5
@author Vaclav Petras <wenzeslaus gmail.com>
9
from grass.pydispatch import dispatcher
12
def _islambda(function):
14
Tests if object is a lambda function.
16
Should work on the most of Python implementations where name of lambda
17
function is not unique.
19
>>> mylambda = lambda x: x*x
20
>>> _islambda(mylambda)
22
>>> _islambda(_islambda)
25
return isinstance(function, type(lambda: None)) and function.__name__== (lambda: None).__name__
31
The signal object is created usually as a instance attribute.
32
However, it can be created anywhere.
34
>>> signal1 = Signal('signal1')
36
The function has to be connected to a signal in order to be called when
37
the signal is emitted. The connection can be done where the function is
38
defined (e. g., a class) but also on some other place, typically,
39
user of a class connects some signal to the method of some other class.
42
... print "from handler1"
43
>>> signal1.connect(handler1)
45
Emitting of the signal is done usually only in the class which has the
46
signal as a instance attribute. Again, generally, it can be emitted
52
The signal can have parameters. These parameters are specified when
53
emitting but should be documented together with the signal (e.g., in the
54
class documentation). Parameters should be keyword arguments and handlers
55
must use these names (if the names cannot be the same, lambda function
56
can be used to overcome this problem).
58
>>> signal2 = Signal('signal2')
59
>>> def handler2(text):
60
... print "handler2: %s" % text
61
>>> signal2.connect(handler2)
62
>>> signal2.emit(text="Hello")
65
Do not emit the same signal with different parameters when emitting at
68
A handler is the standard function, lambda function, method or any other
72
>>> signal2.connect(lambda text:
73
... sys.stdout.write('lambda handler: %s\\n' % text))
74
>>> signal2.emit(text="Hi")
78
The handler function can have only some of the signal parameters or no
79
parameters at all even if the signal has some.
82
... print "from handler3"
83
>>> signal2.connect(handler3)
84
>>> signal2.emit(text="Ciao")
89
It is possible to use signal as a handler. By this, signals can be
90
forwarded from one object to another. In other words, one object can
91
expose signal of some object.
93
>>> signal3 = Signal('signal3')
94
>>> signal3.connect(handler3)
95
>>> signal1.connect(signal3)
100
It is possible to disconnect a particular handler.
102
>>> signal3.disconnect(handler3)
105
>>> signal2.disconnect(handler2)
106
>>> signal2.disconnect(handler3)
107
>>> signal2.emit(text='Hello')
108
lambda handler: Hello
110
# TODO: use the name for debugging
111
def __init__(self, name):
112
"""Creates a signal object.
114
The parameter name is used for debugging.
118
def connect(self, handler, weak=None):
120
Connects handler to a signal.
122
Typically, a signal is defined in some class and the user of this
123
class connects to the signal::
125
from module import SomeClass
127
self.someObject = SomeClass()
128
self.someObject.connect(self.someMethod)
130
Usually, it is not needed to set the weak parameter. This method
131
creates weak references for all handlers but for lambda functions, it
132
automaticaly creates (standard) references (otherwise, lambdas would be
133
garbage collected. If you want to force some behaviour, specify the
136
>>> signal1 = Signal('signal1')
138
>>> signal1.connect(lambda: sys.stdout.write('will print\\n'))
139
>>> signal1.connect(lambda: sys.stdout.write('will print\\n'), weak=False)
140
>>> signal1.connect(lambda: sys.stdout.write('will not print'), weak=True)
146
if _islambda(handler):
150
dispatcher.connect(receiver=handler, signal=self, weak=weak)
152
def disconnect(self, handler, weak=True):
154
Disconnects a specified handler.
156
It is not necessary to disconnect object when it is deleted.
157
Underlying PyDispatcher will take care of connections to deleted
160
>>> signal1 = Signal('signal1')
162
>>> signal1.connect(sys.stdout.write)
163
>>> signal1.disconnect(sys.stdout.write)
165
The weak parameter of must have the same value as for connection.
166
If you not specified the parameter when connecting,
167
you don't have to specify it when disconnecting.
169
Disconnecting the not-connected handler will result in error.
171
>>> signal1.disconnect(sys.stdout.flush) #doctest: +ELLIPSIS
172
Traceback (most recent call last):
173
DispatcherKeyError: 'No receivers found for signal <__main__.Signal object at 0x...> from sender _Any'
175
Disconnecting the non-exiting or unknown handler will result in error.
177
>>> signal1.disconnect(some_function)
178
Traceback (most recent call last):
179
NameError: name 'some_function' is not defined
182
dispatcher.disconnect(receiver=handler, signal=self, weak=weak)
184
# TODO: remove args?, make it work for args?
185
# TODO: where to put documentation
186
def emit(self, *args, **kwargs):
188
Emits the signal which means that all connected handlers will be
191
It is advised to have signals as instance attributes and emit signals
192
only in the class which owns the signal::
196
self.colorChanged = Signal('Abc.colorChanged')
198
def setColor(self, color):
200
self.colorChanged.emit(oldColor=self.Color, newColor=color)
203
Documentation of an signal should be placed to the class documentation
204
or to the code (this need to be more specified).
206
Calling a signal from outside the class is usually not good
207
practice. The only case when it is permitted is when signal is the part
208
of some globaly shared object and permision to emit is stayed in the
211
The parameters of the emit function must be the same as the parameters
212
of the handlers. However, handler can ommit some parameters.
213
The associated parameters shall be documented for each Signal instance.
214
Use only keyword arguments when emitting.
216
>>> signal1 = Signal('signal1')
217
>>> def mywrite(text):
219
>>> signal1.connect(mywrite)
220
>>> signal1.emit(text='Hello')
223
Traceback (most recent call last):
224
TypeError: mywrite() takes exactly 1 argument (0 given)
225
>>> signal1.emit('Hello')
226
Traceback (most recent call last):
227
TypeError: send() got multiple values for keyword argument 'signal'
229
dispatcher.send(signal=self, *args, **kwargs)
232
def __call__(self, *args, **kwargs):
233
"""Allows to emit signal with function call syntax.
235
It allows to handle signal as a function or other callable object.
236
So, the signal can be in the list of fuctions or can be connected as
237
a handler for another signal.
238
However, it is strongly recommended to use emit method for direct
240
The use of emit method is more explicit than the function call
241
and thus it it clear that we are using signal.
243
>>> signal1 = Signal('signal1')
244
>>> def mywrite(text):
246
>>> signal1.connect(mywrite)
247
>>> functions = [signal1, lambda text: mywrite(text + '!')]
248
>>> for function in functions:
249
... function(text='text')
253
The other reason why the function call should not by used when it is
254
possible to use emit method is that this function does ugly hack to
255
enable calling as a signal handler. The signal parameter is deleted
256
when it is in named keyword arguments. As a consequence, when the
257
signal is emitted with the signal parameter (which is a very bad
258
name for parameter when using signals), the error is much more readable
259
when using emit than function call. Concluding remark is that
260
emit behaves more predictable.
262
>>> signal1.emit(signal='Hello')
263
Traceback (most recent call last):
264
TypeError: send() got multiple values for keyword argument 'signal'
265
>>> signal1(signal='Hello')
266
Traceback (most recent call last):
267
TypeError: mywrite() takes exactly 1 argument (0 given)
269
if 'signal' in kwargs:
271
self.emit(*args, **kwargs)
274
if __name__ == '__main__':