Observable Properties (OP) is a powerful mechanism which implement the Observer Pattern.
The mechanism which allows a part of the data contained in models to be observed by entities called Observers. It is fully automatic, as its management is carried out transparently by the base class Model.
Note: | This section mainly focuses on OP in Models. The use of Observer here is anticipated only for examples. The section Observers presents full details about them. |
---|
Here a quick example is shown here, later all details are presented.
from gtkmvc import Model, Observer
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name",)
pass # end of class
# ----------------------------
class MyObserver (Observer):
@Observer.observe("name", assign=True)
def notification(self, model, name, info):
print "'name' changed from", info.old, "to", info.new
return
pass # end of class
# ----------------------------
m = MyModel()
o = MyObserver(m)
m.name = "Tobias"
In models, OPs are declared explicitly, and in observers we define methods which will be called when OPs are changed. In the example, when m.name is changed in the last line, method MyObserver.notification is called automatically to notify the observer. All details about observers will be presented in section Observers, in particular about what assign=True means.
Controllers are also observers, so the OP pattern clicks well with the MVC pattern.
If the example looks smooth and relatively easy, the topic is much more complex. OPs can be concrete or logical, and can be values (like in the previous example), or complex objects like containers or user’s classes. All these differences add complexity which will be described in details in the following sections.
Concrete OPs have values stored inside the model. They are different from Logical OP whose values are calculated, or come from outside (e.g. from a database).
Values of OP are declared as class attributes in models, and OP names are declared in a special class member __observables__.
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
secondname = 'Mario'
surname = "Cavada"
energy = 0.2 # needs holidays!
status = "sleepy"
__observables__ = "name surname energy".split()
pass # end of class
In the example, name, surname and energy are all observable, whereas status is not observable.
Special member __observables__ is a tuple (or a list) of names of class attributes that has to be observable. Names can contain wildcards like * to match any sequence of characters, ? to match one single character, etc. See module fnmatch in Python library for other information about possible use of wildcards in names. Important to say that if wildcards are used, attribute names starting with a double underscore __ will be not matched.
Note
It is also possible (but deprecated!) for the user to add a class variable called __properties__. This variable must be a map, whose elements’ keys are names of properties, and the associated values are the initial values. Using __properties__ is complementary to the use of __observables__, but it is provided for backward compatibility and should be not used in new code. This is an example of usage of deprecated __properties__, but you will not find another in this manual:
from gtkmvc import Model
class MyModelDeprecated (Model):
__properties__ = {
'name' : 'Rob',
}
pass # end of class
This is another example showing the usage of wildcards in names:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
secondname = 'Mario'
surname = "Cavada"
energy = 0.2 # needs holidays!
entropy = 1.0
enology = "good science"
status = "sleepy"
__observables__ = ("*name", "en????y")
pass # end of class
In the example, all attributes but energy and status are declared to be observable.
Things so far are easy enough, but they get a bit complicated when you derive custom models from other custom models. For example, what happens to OP if you derive a new model class from the class MyModel?
In this case the behavior of the OP trusty follows the typical Object Oriented rules:
For example:
from gtkmvc import Model
class Base (Model):
prop1 = 1
__observables__ = ("prop1", )
def __init__(self):
Model.__init__(self)
# this class is an observer of its own properties:
self.register_observer(self)
return
@Model.observe("prop1", assign=True)
def prop1_changed(self, model, name, info):
print model, "prop1 changed from '%s' to '%s'" % (info.old, info.new)
return
pass # end of class
# --------------------------------------------------------
class Der (Base):
prop2 = 2
__observables__ = ("prop2",)
@Model.observe("prop2", assign=True)
def prop2_changed(self, model, name, info):
print self, "prop2 changed from '%s' to '%s'" % (info.old, info.new)
return
pass # end of class
# --------------------------------------------------------
# test code:
b = Base()
d = Der()
d.prop2 *= 10
d.prop1 *= 10
b.prop1 *= 10
When executed, this script generates this output:
<__main__.Der object ...> prop2 changed from '2' to '20'
<__main__.Der object ...> prop1 changed from '1' to '10'
<__main__.Base object ...> prop1 changed from '1' to '10'
Let’s analyse the example.
First, in the Base.__init__ constructor you can see that the instance registers itself as an observer... of itself! As we will see in section Observers, class Model derives from Observer, so all models are also observer.
In the example this is exploited only to write a compact example (it is not needed to define an additional class for the observer). However, in complex designs it is quite common to see models observing them self, or sub-models contained inside them.
Second, method Base.prop1_changed is explicitly marked to observe property prop1.
Third, in class Der only the OP prop2 is declared, as prop1 is inherited from class Base. This is clearly visible in the output:
<__main__.Der object ...> prop1 changed from '1' to '10'
It is possible to change type and default values of OPs in derived class, by re-declaring the OSs. For example:
class Der (Base):
prop1 = 3
prop2 = 2
__observables__ = ("prop?",)
@Observer.observe("prop2", assign=True)
def prop2_changed(self, model, name, info):
print self, "prop2 changed from '%s' to '%s'" % (info.old, info.new)
return
pass # end of class
# --------------------------------------------------------
This would produce the output:
<__main__.Der object ...> prop2 changed from '2' to '20'
<__main__.Der object ...> prop1 changed from '3' to '30'
<__main__.Base object ...> prop1 changed from '1' to '10'
As you can see, d.prop1 overrides the OP prop1 defined in Base (they have different initial values now).
Logical OPs are properties whose values are not necessarily stored in the model, but which are read and written by a pair of getter/setter methods.
This make logical OPs ideal for:
Logical OPs are declared like concrete OPs, but no correspoding attributes have to appear in the class. Their name have to appear only within the special member __observables__. For example:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name", "happiness")
pass # end of class
# ----------------------------
In the example, name is a concrete property, whereas happiness is a logical property, as no corresponding attribute exists in class MyModel.
Note
Notice that names of logical OPs occurring within the special member __observables__ cannot contain wildcards like concrete properties.
The reasons for this limitation is obvious, as wildcards can be used to match only class attributes.)
However, a logical OP’s value is taken from a getter method, and for a read/write OP the values are stored through a setter method. Defining a getter is mandatory, while defining a setter is required only for writable logical OPs.
getter/setter methods can be defined by exploiting decorators, or by exploiting a naming convention.
Decorators @Model.getter and @Model.setter can be used for defining logical OPs getter and setter respectively. The syntax and semantics are very similar to the python @property decorator.
E.g. for logical OP happiness in the previous example:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name", "happiness")
_a_value = 1.0 # support for happiness
@Model.getter
def happiness(self): return self._a_value
@Model.setter
def happiness(self, value): self._a_value = max(1.0, value)
pass # end of class
# ----------------------------
It is possible to define getter/setter methods which serve multiple logical OPs. For example:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name", "happiness", "energy")
_a_value = 1.0 # support for happiness
@Model.getter("happiness", "energy")
def a_getter_for_several_ops(self, name):
if "energy" == name: return 0.1 # constantly need holidays!
return self._a_value
@Model.setter
def happiness(self, value): self._a_value = max(1.0, value)
pass # end of class
# ----------------------------
In the example, the decorator @Model.getter is used with arguments, which have to be the string names of all properties which have to be handled by the decorated method. The method (the getter in this case) will receive the name of the property along with its other arguments.
Use of wildcards is allowed in decorators names, and will match all logical OPs not exactly matched by other decorators. It is an error condition if multiple matches are found when matching logical OPs specified with wildcards. For example this is perfectly legal:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name", "energy", "entropy", "enology")
@Model.getter
def energy(self): return 0.1 # constantly need holidays!
@Model.getter("enology")
def getter1(self, name): return "good science!"
@Model.getter("en*") # matches only remaining 'entropy'
def getter2(self, name):
assert "entropy" == name
return 0
@Model.setter("*") # matches "energy", "entropy", "enology"
def happiness(self, name, value):
print "setter for", name, value
...
return
pass # end of class
# ----------------------------
However, this example is not legal:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
__observables__ = ("energy", "entropy", "enology")
@Model.getter("en*") # matches energy, entropy, and enology
def getter1(self, name): ...
@Model.getter("*") # matches energy, entropy, and enology
def getter2(self, name): ...
pass # end of class
# ----------------------------
The example does not work as ambiguity is found when resolving wilcards.
In some cases, the use of decorators for defining getters/setters can be a limitation. For example, when the model is built dynamically, like when generating proxy classes.
In these and other cases, the framework supports a naming convention which can be used to define implicitly getters and/or setters for logical OPs.
The naming convention applies to Model’s method names which are implicitly declared as getters or setters.
As you see getters/setters can be either specific or generic. In the former case, the getter/setter is specific for one OP. In the latter case, getter/setter is general and will receive the name of the property.
Generic getter/setter will not be called for OPs which have specific getter/setter defined. For example:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
__observables__ = ("energy", "entropy", "enology")
def get_energy_value(self): return 0.1 # constantly need holidays!
# getter for entropy and enology only, as energy has a specific getter
def get__value(self, name): ...
# setter for all properties
def set_value(self, name, value): ...
pass # end of class
# ----------------------------
The first example we presented for decorators could be rewritten as:
from gtkmvc import Model
# ----------------------------
class MyModel (Model):
name = "Roberto"
__observables__ = ("name", "energy", "entropy", "enology")
def get_energy_value(self): return 0.1 # constantly need holidays!
def get_enology_value(self): return "good science!"
def get__value(self, name):
assert "entropy" == name
return 0
def set__value(self, name, value):
print "setter for", name, value
...
return
pass # end of class
# ----------------------------
Of course, since in naming conventions names matters, some names in the example had to be adapted.
In section Supported types of Observable Properties we anticipated that there exist several types of OP*s. In the examples so far we have seen only *value OPs, meaning that observers will be notified of any change of value, i.e. when the OPs are assigned to different values [1].
What would happen if the value of the property would be a complex object like a list, or a user-defined class, and the object would change internally?
For example:
from gtkmvc import Model
class MyModel (Model):
prop1 = [1,2,3]
__observables__ = ("prop1",)
def __init__(self):
Model.__init__(self)
...
return
pass # end of class
m = MyModel()
m.prop1.append(4)
m.prop1[1] = 5
Last two lines of the previous example actually change the OP internally, that is different from assigning a new value to the property like in m.prop1 = [5,4,3,2] that would trigger an assign notifications like those seen in previous examples. Similar problem is found when the property is assigned to a class instance, and then a method that change the instance is called.
In this section only the model-side will be presented. In the section dedicated to observers, we will see how changes occurring to this objects are notified.
The framework MVC-O provides a full support for python mutable containers like lists and maps. For example:
from gtkmvc import Model, Observer
# ----------------------------------------------------------------------
class MyModel (Model):
myint = 0
mylist = []
mymap = {}
__observables__ = ("my*", )
pass # end of class
This covers those cases where you have your OPs holding mutable sequence values.
What if the value is a user-defined class instance? Suppose a class has a method changing the content instance. The idea is that observers are notified when the method is called, with the possibility to choose if being notified before or after the call.
However, how can the user declare that method M does changes the instance? Two mechanism are provided by the framework:
Examples for new classes:
from gtkmvc import Model, Observable
# ----------------------------------------------------------------------
class AdHocClass (Observable):
def __init__(self):
Observable.__init__(self)
self.val = 0
return
# this way the method is declared as 'observed':
@Observable.observed
def change(self): self.val += 1
# this is NOT observed (and it does not change the instance):
def is_val(self, val): return self.val == val
pass #end of class
# ----------------------------------------------------------------------
class MyModel (Model):
obj = AdHocClass()
__observables__ = ("obj",)
pass # end of class
As you can see, declaring a class as observable is as simple as deriving from gtkmvc.Observable and decorating those class methods that must be observed with the decorator gtkmvc.Observable.observed (decorators are supported by Python version 2.4 and later only).
However, sometimes we want to reuse existing classes and it is not possible to derive them from gtkmvc.Observable. In this case declaration of the methods to be observed can be done at time of declaration of the corresponding OP. In this case the value to be assigned to the OP must be a triple (class, instance, method_names>, where:
For example:
from gtkmvc import Model
#----------------------------------------------------------------------
# This is a class the used cannot/don't want to change
class HolyClass (object):
def __init__(self): self.val = 0
def change(self): self.val += 1
pass #end of class
# ----------------------------------------------------------------------
class MyModel (Model):
obj = (HolyClass, HolyClass(), ('change',))
__observables__ = ("obj",)
pass # end of class
Finally, OP can hold special values that are signals that can be used to notify observers that certain events occurred.
To declare an OP as a signal, the value of the OP must be an instance of class gtkmvc.Signal. To notify an event, the model can then invoke method emit of the OP. Emitting a signal can carry an optional argument.
For example:
from gtkmvc import Model, Signal
# ----------------------------------------------------------------------
class MyModel (Model):
sgn = Signal()
__observables__ = ("sgn",)
pass
if __name__ == "__main__":
m = MyModel()
m.sgn.emit() # we emit a signal
m.sgn.emit("hello!") # with argument
pass
In the examples, there are several examples that show how different types of OPs can be used. Of course all available types can be used in all available kind of model classes, with or without multi-threading support.
So far in our examples, all OPs were class members:
from gtkmvc import Model
class MyModel (Model):
prop1 = 10
prop2 = []
__observables__ = ("prop?",)
pass # end of class
Using class vs instance attributes is not an issue when they are assigned:
m1 = MyModel()
m2 = MyModel()
m1.prop1 = 5
m2.prop1 = 15
In this case after the assignment m1 and m2 will have their own value for attribute prop1.
However, when dealing with attributes whose type is a class instances, like for example a list, you must keep in mind the attribute sharing.
m1.prop2.append(1)
print m2.prop2 # prints [1] !
If attribute sharing is not what you want, simply assign OPs in the model’s constructor:
class MyModel (Model):
prop1 = 10
prop2 = [] # may be any value actually
__observables__ = ("prop?",)
def __init__(self):
MyModel.__init__(self)
self.prop2 = []
return
pass # end of class
Now m1.prop2 and m2.prop2 are different objects, and sharing no longer occurs.
Section Notes
[1] | Actually there exist spurious assign notifications, which are issued also when there is no change in the value of an OP, e.g. when an OP is assigned to itself. |
When dealing with logical OPs and in particular with getter/setter pairs, there are complex conditions and behaviours of the framework which may be confusing. Here those conditions are listed and explained.
A logical OP has no associated getter.
Getters are mandatory for logical OPs.
OP matched by multiple getters/setters.
Each OP must be matched exactly with one getter and optionally with one setter. It is an error condition any multiple matching.
Exactly the same pattern was used for one or more getters (setters).
This is a specialization of previous case (multiple matching).
Warning are issued only if the devlopers enables them. In released applications, warnings should be not visible by default, to avoid worrying the application user.
When developing, it is important to enable warnings, though.
A setter has no corresponing getter.
When a setter has no getter, the setter is simply ignored.
Getter/setter defined for concrete OPs.
When a getter and/or a setter is defined for a concrete OP (not a logical OP), the getter/setter are ignored.
Getter/setter defined for non-existing logical OP.
Getters/setters whose pattern do not match any existing logical OP, are ignored.
Classes derived from Model, that exports OPs, have several special members. Advanced users might be interested in overriding some of them, but in general they should be considered as private members. They are explained here for the sake of completeness.
For further details about this topic see meta-classes PropertyMeta and ObservablePropertyMeta from package support.