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. |