Package: observable-properties
Observable managed attributes (also known as "observable properties")In summary:Declare observable properties using the@observabledecorator instead of@property.Subscribe/unsubscribe any number of callback functions to observable properties.If the value of an observable property changes, all subscribed callbacks are executed.How to useDeclare observable properties in your classUse the@observabledecorator as you do with@property:fromobservable_propertiesimportobservableclassTest():def__init__(self):self._value=0@observabledefvalue(self):returnself._value@value.setterdefvalue(self,value):self._value=valueNeedless to say, observable properties are also regular properties and they work just the same:t=Test()t.value=1000print(t.value)Subscribe to property changesIn the context of this library, anobserveris a callback function to be executed
when the value of a property is overwritten in a given object. For example:defobserver(instance,property_name,new_value):print(f"{instance.__class__.__name__}.{property_name}={new_value}")To start observing a property, callsubscribe()passing the instance and property name to observe along with the callback function:fromobservable_propertiesimportsubscribesubscribe(observer,t,"value")Which means "subscribeobservertot.value".
Now, the observer is executedafterthe value of the observed property changes at the observed object:t.value=2000prints:Test.value = 2000An observer may also subscribe in this way to be executedbeforethe value changes:defobserver_before(instance,property_name,new_value):old_value=getattr(instance,property_name)print(f"{instance.__class__.__name__}.{property_name}changes from{old_value}to{new_value}")subscribe(observer_before,t,"value",before=True)t.value=3000prints:Test.value changes from 2000 to 3000
Test.value = 3000Note that bothobserverandobserver_beforewere executed.Additionally:Observersare not allowedto change the value of the observed property.ObservablePropertyErroris raised in such a case.The same observer can subscribe to many objects and properties.Any number of observers can subscribe to the same object and property.
All of them will run. The execution order depends on subscription order.An observer may be executed before or after a value changes,but not both.
Use different observers for that.UnsubscribeTo stop observing a property, callunsubscribe()with the same parameters that were given tosubscribe()(except forbefore=):fromobservable_propertiesimportunsubscribeunsubscribe(observer,t,"value")Syntactic sugarTo speed things up, derive your class from theObservableclass (note the capital letter).
The declaration itself does not differ too much from the previous example:fromobservable_propertiesimportObservableclassObservableTest(Observable):def__init__(self):self._value=0@observabledefvalue(self):returnself._value@value.setterdefvalue(self,value):self._value=valueObservable classes offersubscribe()andunsubscribe()methodswith a slightly different
syntax to their function counterparts:ot=ObservableTest()ot.subscribe("value",observer)ot.value=900ot.unsubscribe("value",observer)ot.value=500prints:ObservableTest.value = 900Observable changes triggered fromnon-settermethodsA publicsetterisnot mandatoryfor observable properties of anObservableinstance.The class itself can notify changes in observable properties by placing a call to the inherited method_observable_notify(),
which executes subscribed observers. For example:classDuplicator(Observable):def__init__(self,initial_value=1):self._value=initial_value@observabledefvalue(self):returnself._valuedefduplicate(self):new_value=self._value*2# Trigger subscribed observers before actual changeself._observable_notify("value",new_value,True)self._value=new_value# Trigger subscribed observers after actual changeself._observable_notify("value",new_value)dup=Duplicator(100)dup.subscribe("value",observer)dup.subscribe("value",observer_before,True)dup.duplicate()prints:Duplicator.value changes from 100 to 200
Duplicator.value = 200A common use case is to compute observable properties "on the fly":classProduct(Observable):def__init__(self):self._a=0;self._b=0@observabledefvalue(self):returnself._a*self._bdefset(self,a,b):self._a=aself._b=bself._observable_notify("value",self.value)mult=Product()mult.subscribe("value",observer)mult.subscribe("value",observer_before,True)mult.set(3,5)prints:Multiplier.value = 15Notethatobserver_beforewasnot executedin the later example
because the resulting value of the observable property is unknown before the computation itself takes place.Other notesBothsubscribe()andunsubscribe()raiseObservablePropertyErroron non-observable
or non-existing properties.Subscribing twice to the same object and property has no effect.Unsubscribing to non-subscribed properties has no effect, butunsubscribe()returns False.Objects hold strong references to observers. Deleting an observer does not
prevent it from running. Unsubscribe first.Coroutines are accepted as observers. They are executed by the means ofasyncio.run()