signal.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Mon Mar 11 18:39:13 2013
  4. @author Vaclav Petras <wenzeslaus gmail.com>
  5. """
  6. from grass.pydispatch import dispatcher
  7. def _islambda(function):
  8. """Tests if object is a lambda function.
  9. Should work on the most of Python implementations where name of lambda
  10. function is not unique.
  11. >>> mylambda = lambda x: x*x
  12. >>> _islambda(mylambda)
  13. True
  14. >>> _islambda(_islambda)
  15. False
  16. """
  17. return isinstance(function, type(lambda: None)) and function.__name__== (lambda: None).__name__
  18. class Signal(object):
  19. """
  20. >>> signal1 = Signal('signal1')
  21. >>> def handler1():
  22. ... print "from handler1"
  23. >>> signal1.connect(handler1)
  24. >>> signal2 = Signal('signal2')
  25. >>> def handler2(text):
  26. ... print "handler2: %s" % text
  27. >>> signal2.connect(handler2)
  28. >>> signal1.emit()
  29. from handler1
  30. >>> signal2.emit(text="Hello")
  31. handler2: Hello
  32. >>> import sys
  33. >>> signal2.connect(lambda text:
  34. ... sys.stdout.write('lambda handler: %s\\n' % text))
  35. >>> signal2.emit(text="Hi")
  36. handler2: Hi
  37. lambda handler: Hi
  38. >>> def handler3():
  39. ... print "from handler3"
  40. >>> signal2.connect(handler3)
  41. >>> signal2.emit(text="Ciao")
  42. handler2: Ciao
  43. lambda handler: Ciao
  44. from handler3
  45. >>> signal3 = Signal('signal3')
  46. >>> signal3.connect(handler3)
  47. >>> signal1.connect(signal3)
  48. >>> signal1.emit()
  49. from handler1
  50. from handler3
  51. >>> signal3.disconnect(handler3)
  52. >>> signal1.emit()
  53. from handler1
  54. >>> signal2.disconnect(handler2)
  55. >>> signal2.disconnect(handler3)
  56. >>> signal2.emit(text='Hello')
  57. lambda handler: Hello
  58. """
  59. def __init__(self, name):
  60. self._name = name
  61. def connect(self, handler, weak=None):
  62. """
  63. >>> signal1 = Signal('signal1')
  64. >>> import sys
  65. >>> signal1.connect(lambda: sys.stdout.write('will print\\n'))
  66. >>> signal1.connect(lambda: sys.stdout.write('will print\\n'), weak=False)
  67. >>> signal1.connect(lambda: sys.stdout.write('will not print'), weak=True)
  68. >>> signal1.emit()
  69. will print
  70. will print
  71. """
  72. if weak is None:
  73. if _islambda(handler):
  74. weak = False
  75. else:
  76. weak = True
  77. dispatcher.connect(receiver=handler, signal=self, weak=weak)
  78. def disconnect(self, handler, weak=True):
  79. """
  80. >>> signal1 = Signal('signal1')
  81. >>> import sys
  82. >>> signal1.connect(sys.stdout.write)
  83. >>> signal1.disconnect(sys.stdout.write)
  84. >>> signal1.disconnect(sys.stdout.flush) #doctest: +ELLIPSIS
  85. Traceback (most recent call last):
  86. DispatcherKeyError: 'No receivers found for signal <__main__.Signal object at 0x...> from sender _Any'
  87. >>> signal1.disconnect(some_function)
  88. Traceback (most recent call last):
  89. NameError: name 'some_function' is not defined
  90. >>> signal1.emit()
  91. """
  92. dispatcher.disconnect(receiver=handler, signal=self, weak=weak)
  93. # TODO: remove args?, make it work for args?
  94. def emit(self, *args, **kwargs):
  95. """
  96. >>> signal1 = Signal('signal1')
  97. >>> def mywrite(text):
  98. ... print text
  99. >>> signal1.connect(mywrite)
  100. >>> signal1.emit(text='Hello')
  101. Hello
  102. >>> signal1.emit()
  103. Traceback (most recent call last):
  104. TypeError: mywrite() takes exactly 1 argument (0 given)
  105. >>> signal1.emit('Hello')
  106. Traceback (most recent call last):
  107. TypeError: send() got multiple values for keyword argument 'signal'
  108. """
  109. dispatcher.send(signal=self, *args, **kwargs)
  110. def __call__(self, *args, **kwargs):
  111. """Allows to emit signal with function call syntax.
  112. It allows to handle signal as a function or other callable object.
  113. So, the signal can be in the list of fuctions or can be connected as
  114. a handler for another signal.
  115. However, it is strongly recommended to use emit method for direct
  116. signal emitting.
  117. The use of emit method is more explicit than the function call
  118. and thus it it clear that we are using signal.
  119. >>> signal1 = Signal('signal1')
  120. >>> def mywrite(text):
  121. ... print text
  122. >>> signal1.connect(mywrite)
  123. >>> functions = [signal1, lambda text: mywrite(text + '!')]
  124. >>> for function in functions:
  125. ... function(text='text')
  126. text
  127. text!
  128. The other reason why the function call should not by used when it is
  129. possible to use emit method is that this function does ugly hack to
  130. enable calling as a signal handler. The signal parameter is deleted
  131. when it is in named keyword arguments. As a consequence, when the
  132. signal is emitted with the signal parameter (which is a very bad
  133. name for parameter when using signals), the error is much more readable
  134. when using emit than function call. Concluding remark is that
  135. emit behaves more predictable.
  136. >>> signal1.emit(signal='Hello')
  137. Traceback (most recent call last):
  138. TypeError: send() got multiple values for keyword argument 'signal'
  139. >>> signal1(signal='Hello')
  140. Traceback (most recent call last):
  141. TypeError: mywrite() takes exactly 1 argument (0 given)
  142. """
  143. if 'signal' in kwargs:
  144. del kwargs['signal']
  145. self.emit(*args, **kwargs)
  146. if __name__ == '__main__':
  147. import doctest
  148. doctest.testmod()