/usr/lib/python3/dist-packages/obsub.py is in python3-obsub 0.2-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | '''
This is an implementation of the observer pattern. It uses function
decorators to achieve the desired event registration mechanism.
For further reference, see
http://en.wikipedia.org/wiki/Observer_pattern
The idea is based on this thread:
http://stackoverflow.com/questions/1904351/python-observer-pattern-examples-tips
'''
import functools
import inspect
try:
# use python3 signatures if available
# this takes care of enforcing the correct signature at call time and
# provides the correct default arguments
from inspect import signature
except ImportError: # pragma: no cover
# python2 has no support for signatures
def signature(fn):
return None
__all__ = ['event']
__version__ = '0.2'
class event(object):
'''
This class serves as a utility to decorate a function as an event.
The following example demonstrates its functionality in an abstract way.
A class method can be decorated as follows:
>>> class A(object):
... def __init__(self, name):
... self.name = name
...
... @event
... def progress(self, first, second):
... print("Doing something...")
A.progress is the event. It is triggered after executing the code in the
decorated progress routine.
Now that we have a class with some event, let's create an event handler.
>>> def handler(self, first, second):
... print("%s %s and %s!" % (first, self.name, second))
Note that the handler (and signal calls) must have the signature defined
by the decorated event method.
This handler only greets the object that triggered the event by using its
name attribute. Let's create some instances of A and register our new
event handler to their progress event.
>>> a = A("Foo")
>>> b = A("Bar")
>>> a.progress += handler
>>> b.progress += handler
Now everything has been setup. When we call the method, the event will be
triggered:
>>> a.progress("Hello", "World")
Doing something...
Hello Foo and World!
>>> b.progress(second="Others", first="Hi")
Doing something...
Hi Bar and Others!
What happens if we disobey the call signature?
>>> c = A("World")
>>> c.progress(second="World") # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
TypeError: progress() missing 1 required positional argument: 'first'
Class based access is possible as well:
>>> A.progress(a, "Hello", "Y")
Doing something...
Hello Foo and Y!
Bound methods keep the instance alive:
>>> f = a.progress
>>> import weakref, gc
>>> wr = weakref.ref(a)
>>> del a
>>> c=gc.collect()
>>> assert wr() is not None
>>> f("Hi", "Z")
Doing something...
Hi Foo and Z!
If we delete the hard reference to the bound method and run the garbage
collector (to make sure it is run at all), the object will be gone:
>>> del f
>>> c=gc.collect()
>>> assert wr() is None
'''
def __init__(self, function):
'''
Constructor.
* function -- The function to be wrapped by the decorator.
'''
# Copy docstring and other attributes from function
functools.update_wrapper(self, function)
self.__signature__ = signature(function)
# Used to enforce call signature even when no slot is connected.
# Can also execute code (called before handlers)
self.__function = function
def __set__(self, instance, value):
'''
This is a NOP preventing that a boundevent instance is stored.
This prevents operations like `a.progress += handler` to have
side effects that result in a cyclic reference.
http://stackoverflow.com/questions/18287336/memory-leak-when-invoking-iadd-via-get-without-using-temporary
'''
pass
def __get__(self, instance, owner):
'''
Overloaded __get__ method. Defines the object resulting from
a method/function decorated with @event.
See http://docs.python.org/reference/datamodel.html?highlight=__get__#object.__get__
for a detailed explanation of what this special method usually does.
* instance -- The instance of event invoked.
* owner -- The owner class.
'''
# this case corresponds to access via the owner class:
if instance is None:
@functools.wraps(self.__function)
def wrapper(instance, *args, **kwargs):
return self.__get__(instance, owner)(*args, **kwargs)
else:
wrapper = functools.wraps(self.__function)(boundevent(instance, self.__function))
wrapper.__signature__ = self.__signature__
return wrapper
class boundevent(object):
'''Private helper class for event system.'''
def __init__(self, instance, function):
'''
Constructor.
* instance -- the instance whose member the event is
'''
self.instance = instance
self.__function = function
self.__key = ' ' + function.__name__
@property
def __event_handlers(self):
if self.__key not in self.instance.__dict__:
self.instance.__dict__[self.__key] = []
return self.instance.__dict__[self.__key]
def __iadd__(self, function):
'''
Overloaded += operator. It registers event handlers to the event.
* function -- The right-hand-side argument of the operator; this is the
event handling function that registers to the event.
'''
# Add the function as a new event handler
self.__event_handlers.append(function)
# Return the boundevent instance itself for coherent syntax behaviour
return self
def __isub__(self, function):
'''
Overloaded -= operator. It removes registered event handlers from
the event.
* function -- The right-hand-side argument of the operator; this is the
function that needs to be removed from the list of event handlers.
'''
# Remove the function from the list of registered event handlers
self.__event_handlers.remove(function)
# Return the boundevent instance itself for coherent syntax behaviour
return self
def __call__(self, *args, **kwargs):
'''
Overloaded call method; it defines the behaviour of boundevent().
When the event is called, all registered event handlers are called.
* *args -- Arguments given to the event handlers.
* **kwargs -- Keyword arguments given to the event handlers.
'''
# Enforce signature and possibly execute entry code. This makes sure
# any inconsistent call will be caught immediately, independent of
# connected handlers.
result = self.__function(self.instance, *args, **kwargs)
# Call all registered event handlers
for f in self.__event_handlers[:]:
f(self.instance, *args, **kwargs)
return result
|