Observable Properties¶
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 Observable Properties¶
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.
Concrete OP and inheritance¶
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:
- Any concrete OP in base class are inherited by derived classes.
- Derived class can override any concrete OP in base classes.
- If multiple base classes defines the same OP, only the first OP will be accessible from the derived class.
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 Observable Properties¶
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:
- Values calculated out of other OPs (which can be either logical or concrete).
- Values living outside the model, like e.g. in a database, or in a remote server.
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.
Use of decorators for defining getters and/or setters¶
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.
Use of naming convention for defining getters and/or setters¶
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.
- get_<prop_name>_value(self): A specific getter for OP <prop_name>.
- set_<prop_name>_value(self, value): A specific setter for OP <prop_name>.
- get__value(self, name): A generic getter receiving the name of the property to be get.
- set__value(self, name, value): A generic setter receiving the name of the property to be set.
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.
Types of Observable Properties¶
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.
Supported objects¶
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.
Mutable containers¶
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.
Class Instances¶
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:
- For already existing classes. In this cases the declaration occurs when the instance is assigned to the OP in the model.
- For ad-hoc and new classes. In this case the method will be declared as Observable at the class level, through a special decorator provided by the framework. This is the preferable manner.
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:
- class
- Is the
class
of the object to be observed. - instance
- Is the object to be observed.
- method_names
- Is a tuple of strings, representing the method names of the instance to be observed.
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
Signals¶
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.
Class vs Instance members as OPs¶
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. |
Error and Warning conditions with logical OPs¶
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.
Error conditions¶
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 conditions¶
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.
Special members for Observable Properties¶
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.
__observables__
- A class (static) member that lists property
names. This must be provided as either a tuple or a list by the
user. Wilcards in names can be used to match property names, but
properties with names starting with a double underscore
__
will be not matched. __properties__
- (Deprecated, do not use anymore) A dictionary mapping observable properties names and their initial value. This variable has been substituted by __observables__.
__derived_properties__
- (Deprecated) Automatically generated static member that maps the OPs exported by all base classes. This does not contain OPs that the class overrides.
_prop_<property_name>
- This is an
auto-generated variable holding the property value. For example,
a property called
x
will generate a variable called_prop_x
. get_prop_<property_name>
- This public method
is the getter for the property. It is automatically generated only
if the user does not define one. This means that the user can change
the behavior of it by defining their own method. For example, for
property
x
the method isget_prop_x
. This method gets only self and returns the corresponding property value. set_prop_<property_name>
- This public method
is customizable like
get_prop_<property_name>
. This does not return anything, and gets self and the value to be assigned to the property. The default auto-generated code also calls methodgtkmvc.Model.notify_property_change
to notify the change to all registered observers.
For further details about this topic see meta-classes PropertyMeta
and ObservablePropertyMeta
from package support
.