nmevent v0.3.2 - C#-like implementation of the Observer pattern
This is a Python module nmevent, simple C#-like implementation of the Observer pattern (http://en.wikipedia.org/wiki/Observer_pattern). It’s main purpose and goal is to allow developers to use events with C#-like syntax in their Python classes.
The most straightfoward way to use nmevent is this:
>>> import nmevent
>>> class ExampleClass(object):
... def __init__(self):
... self.event = nmevent.Event()
...
... def do_something(self):
... self.event(self)
...
>>> def handler(sender, **keywords):
... print "event occured"
...
>>> example = ExampleClass()
>>> example.event += handler
>>> example.do_something()
event occured
It should be noted, that event doesn’t necessarily need to be an object attribute. Event instance is basically just a callable object that works as a sort of “dispatch demultiplexer”.
This usage, however, isn’t very C#-like. In C#, events are declared in class scope and that’s why the Event class also supports the descriptor protocol (you can use the same way you use the built-in property object).
>>> from nmevent import Event
>>> class ExampleClass(object):
... event = Event()
...
... def _do_something(self):
... self.event()
...
>>> def handler(sender, **keywords):
... pass
...
>>> example = ExampleClass()
>>> example.event += handler
Perhaps this looks even more straightfoward than instantiating Event in object’s constructor, but there’s actually lot more going on under hood this time.
Finally, there is the Property descriptor and the associated nmproperty() function decorator, which work very much like the built-in property object and decorator, except it can optionally call a callback function if the property’s value changes after calling the setter function. It can work in tandem with the with_events() class decorator, which decorates the class with property change events and connects them to the instances of Property class. It also creates events for the built-in property objects, but you have to raise the events yourself in the setter function or elsewhere.
>>> @nmevent.with_events
... class ExampleClass(object):
... @nmevent.nmproperty
... def x(self):
... return self._x
...
... @x.setter
... def x(self, value):
... self._x = value
...
... @property
... def y(self):
... return self._y
...
... @y.setter
... def y(self, value):
... old_value, self._y = self._y, value
... self.y_changed(old_value = old_value)
...
... def __init__(self):
... self._x = None
... self._y = None
...
>>> def handler(sender, **keywords):
... print "x changed"
...
>>> example = ExampleClass()
>>> example.x_changed += handler
>>> example.x = 10 # handler gets called
x changed
Copyright (c) 2010, Jan Milík.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope the it will be useful, but WITHOUT ANY WARRANTY; without event the implied warranty of MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Fixed a major bug, which caused an unbound event not to be actually bound when called with an object instance as the first argument.
Added the with_properties() class decorator, which automatically decorates a class with “private” attributes for each property and automatic getters and setters where either one of them is missing.
Value of this module variable is the name of the attribute that is used to store event data in object instances when Event is used as an descriptor.
If the class of your object uses the __slots__ attribute, don’t forget to include the value of this variable in the sequence you assign to __slots__.
Example:
>>> class Example(object):
... __slots__ = ('foo', 'bar', nmevent.EVENTS_ATTRIBUTE, )
... event = nmevent.Event()
Subject in the observer pattern.
This class represents the subject in the observer pattern. It keeps a collection of handlers, which correspond to the observers in the observer pattern.
Usage:
>>> class Example(object):
... def __init__(self):
... self.event = Event()
...
... def fire(self):
... self.event(self)
...
Adds a handler (observer) to this event.
__iadd__ attribute of this class is just an alias of this method, so the last two of the following statements are equivalent:
>>> event.add_handler(handler) # doctest: +SKIP
>>> event += handler # doctest: +SKIP
Returns True if handler is this event’s handler.
Returns True if the specified handler is contained in the collection of this event’s handlers.
Removes a handler from this event.
Removes a handler (observer) from the collection of this event’s handlers.
Bound or unbound event.
In Python, unbound actually means bound to a class. Bound means bound to both a class and an instance. Instances of this class cannot be bound to the None object, because it is used to indicate that the event is unbound.
This class is meant to be instantiated either through the Event‘s descriptor protocol, or by the Event.bind() method.
Parameters: |
|
---|
Instance this event is bound to.
The following condition must be always true:
>>> isinstance(self.im_sender, self.im_class) # doctest: +SKIP
Attempts to bind the instance event to an instance.
Note that this method silently fails and returns self when the event is already bound. This is by design and is meant to unify the Event‘s and InstanceEvent‘s protocol.
Eventful property descriptor.
This class is not meant to be used directly by the client code, even though nothing stops you from doing so. Instances of this class are supposed to be created by the nmproperty() decorator.
Parameters: |
|
---|
Usage:
>>> class Example(object):
... @nmevent.nmproperty
... def x(self):
... return self._x
...
... @x.setter
... def x(self, value):
... self._x = value
...
... x_changed = nmevent.Event()
... x.changed = x_changed
...
... def __init__(self):
... self._x = 0
...
>>> def handler(sender, **keywords):
... print "x changed, old value: %r, new value: %r" % (keywords['old_value'], sender.x)
...
>>> example = Example()
>>> example.x_changed += handler
>>> example.x = 42
x changed, old value: 0, new value: 42
Getter function.
If non-None, this function gets called every time the value of the property is retrieved. The return value of this function is returned as the value of the property.
Setter function.
If non-None, this function gets called every time the value of the property is set. This function is responsible for actually storing the value somehow.
If this attribute is None, the property is considered read-only.
Value change notification event.
If non-None, this event will be raised every time this property is set to a different value.
Property value change notification event.
If none-None, this event will be raised every time this property is set to a different value. Unlike the changed event, however, the handlers of this event will also be passed keyword name, which will contain the name of this property (see Property.name).
This is can be used when you need to watch for change of any property, but need to know which one changed.
This event has been inspired by the .NET framework’s INotifyPropertyChanged interface (see http://msdn.microsoft.com/en-US/library/system.componentmodel.inotifypropertychanged.aspx)
Method decorator to set the delete function.
Works exatcly like the built-in @property.deleter.
Parameter: | function – the property deleter function |
---|---|
Returns: | self |
The name of the property.
This should be the name of an object’s attribute that holds the property. The name is guessed by retrieving the name of the getter function if present.
Sets the setter function and returns self.
This function is meant to be used as a method decorator, even though it can be called directly to set the setter function of its property.
Usage:
>>> class ExampleClass(object):
... def x(self):
... return self._x
...
... # @nmevent.nmproperty could have been used,
... # but this way it is more obvious what x is.
... x = nmevent.Property(x)
...
... # This is how it's supposed to be used.
... @x.setter
... def set_x(self, value):
... self._x = value
...
... # Also works, but looks ugly.
... x.setter(set_x)
...
Parameter: | function – the property setter function |
---|---|
Returns: | self |
Collection of callbacks.
Adds a callback to callection.
Parameter: | callback – callable object to be added |
---|
Returns True is callback is in the collection.
Parameter: | callback – callback to check for |
---|
Removes a callback from the collection.
Parameter: | callback – callback to be removed |
---|
Eventful property decorator.
Creates new Property object using the decorated method as the getter function. Setter and deleter functions can be set by the Property.setter() and Property.deleter() decorators.
This decorator is called nmproperty() to avoid name conflict with the built-in property function and decorator.
Usage:
>>> class ExampleClass(object):
... @nmevent.nmproperty
... def x(self):
... return self._x
...
... @x.setter
... def x(self, value):
... self._x = value
...
... x_changed = nmevent.Event()
... x.changed = x_changed
...
... def __init__(self):
... self._x = None
...
>>> def handler(sender, **keywords):
... print "handler called"
...
>>> example = ExampleClass()
>>> example.x_changed += handler # "handler" will be called when the value of x changes
>>> example.x = 10 # value of x changed, "handler" gets called
handler called
The Property.changed events can be automatically created and set by the with_events() decorator when used on the class.
Parameter: | function – function to be used as the property getter function |
---|---|
Returns: | new Property object |
Decorates a class with some automatic event slots.
Parameter: | clss – class object to be decorated |
---|---|
Returns: | decorated class |
Automatically adds property change notification events of the name “x_changed”, where x is the name of the property.
Usage:
>>> @nmevent.with_events
... class Example(object):
... @nmevent.nmproperty
... def x(self):
... return self._x
...
... @x.setter
... def x(self, value):
... self._x = value
...
... def __init__(self):
... self._x = 0
...
>>> def x_changed_handler(sender, **keywords):
... old_value = keywords['old_value']
... print "x changed; %r -> %r" % (old_value, sender.x)
...
>>> def property_changed_handler(sender, **keywords):
... old_value = keywords['old_value']
... name = keywords['name']
... print "property '%s' changed, %r -> %r" % (name, old_value, sender.x)
...
>>> example = Example()
>>> example.x_changed += x_changed_handler
>>> example.property_changed += property_changed_handler
>>> example.x = 42
x changed; 0 -> 42
property 'x' changed, 0 -> 42
In the example above, the with_events() decorator automatically decorates the class with an x_changed event and property_changed event connects them to the instance of Property class created by the nmproperty() decorator.
Simply put, the class has x_changed event and property_changed events that are raised when the value of Example.x changes. x_changed gets called only when Example.x changes, property_changed gets called when any property changes.
Decorates a class with automatic “private” attributes.
Parameter: | clss – class object to decorate. |
---|---|
Returns: | decorated class |
For every Property instance within the class’ dictionary, it creates a setter and getter (unless they are already set to a non-None value). These setters and getters use “private” attributes - attributes that the same name as the property prepended with an underscore. In other words, for a property foo, you get an attribute called _foo where the actual value is stored.
Usage:
>>> @nmevent.with_properties
... class Example(object):
... foo = nmevent.Property()
... foo_changed = nmevent.Event()
... foo.changed = foo_changed
...
>>> def on_foo_changed(sender, old_value):
... print "foo changed"
...
>>> x = Example()
>>> x.foo_changed += on_foo_changed
>>> x.foo = 42 # on_foo_changed gets called
foo changed
Used together with with_events:
>>> @nmevent.with_events
... @nmevent.with_properties
... class NextExample(object):
... bar = nmevent.Property()
...
Connects observer’s handlers to subject’s events by their names.
Parameters: |
|
---|---|
Returns: | list of tuples of events and respective handlers |
Handler methods are recognized by their name. Their name must start with the prefix and the continue with the name of the event.
This function can also connect handlers to the events of subject’s attributes, even nested ones. To connect a handler to a “nested event”, separate the attribute names and the event name by double underscore (“__”). For instance, to handle the “z” event of attribute “y” of attribute “x”, name your handler “on_x__y__z” (assuming you use the default prefix “on_“).
>>> class Observer(object):
... def on_x_happened(self, *senders, **keywords):
... print "x happened"
...
... def on_y_happened(self, *senders, **keywords):
... print "y happened"
...
... def on_attr__x_happened(self, *senders, **keywords):
... print "attr.x happened"
...
>>> class Observable(object):
... x_happened = nmevent.Event()
... y_happened = nmevent.Event()
...
>>> observer = Observer()
>>> observable = Observable()
>>> observable.attr = Observable()
>>> nmevent.adapt(observer, observable) #doctest: +ELLIPSIS
[...]
>>> observable.x_happened()
x happened
>>> observable.y_happened()
y happened
>>> observable.attr.x_happened()
attr.x happened