Package: binding
BindingThis package brings property binding to Python.
It allows you to bind attributes of one object to other attributes of itself or other objects like so:label.text.bind(model.value)That means, whenevermodel.valueis changed,label.textchanges as well.InstallationThis package can be installed using the Python package installerpip:python3-mpipinstallbindingAlternatively you can find thesource codeon GitHub.UsageYou can apply binding in two different ways: automatic updates using bindable properties or by calling an update functional explicitely.
Furthermore, you can specify the binding direction as well as converter functions.The following examples give a more detailed explanation.
The code snippets build upon each other and are meant to be called in succession.Bindable propertiesIf you have control over the class implementation, you can introduce aBindablePropertyfor the respective attributes. It will intercept each write access the attribute and propagate the changed value to bound properties:frombindingimportBindablePropertyclassPerson:name=BindableProperty()def__init__(self,name=None):self.name=nameclassCar:driver=BindableProperty()def__init__(self,driver=None):self.driver=driverperson=Person('Robert')car=Car()car.driver.bind(person.name)assertcar.driver==person.name=='Robert'person.name='Bob'assertcar.driver==person.name=='Bob'Binding with non-bindable attributesSuppose you have a class which you cannot or don't want to change.
That means it has noBindablePropertyto observe value changes.
You can bind its attributes nevertheless:classLicense:def__init__(self,name=None):self.name=namelicense=License()license.name.bind(person.name)person.name='Todd'assertlicense.name==person.name=='Todd'But if the license name is changed, there is noBindablePropertyto notice write access to its value.
We have to manually trigger the propagation to bound objects.frombindingimportupdatelicense.name='Ben'assertperson.name!=license.name=='Ben'update()assertperson.name==license.name=='Ben'One-way bindingThe.bind()method registers two-way binding.
But you can also specify one-way binding using.bind_from()or.bind_to(), respectively.
In the following examplecarreceives updatesperson, but not the other way around.person=Person('Ken')car=Car()car.driver.bind_from(person.name)assertcar.driver==person.name=='Ken'person.name='Sam'assertcar.driver==person.name=='Sam'car.driver='Seth'assertcar.driver!=person.name=='Sam'Likewise you can specify forward binding to letpersonbe updated whencarchanges:person=Person('Keith')car=Car()car.driver.bind_to(person.name)assertcar.driver==person.name==Nonecar.driver='Kent'assertcar.driver==person.name=='Kent'person.name='Grant'assertcar.driver!=person.name=='Grant'ConvertersFor all types of binding - forward, backward, two-way, via bindable properties or non-bindable attributes - you can define converter functions that translate values from one side to another.
The following example demonstrates the conversion between Celsius and Fahrenheit.classTemperature:c=BindableProperty()f=BindableProperty()def__init__(self):self.c=0.0self.f=0.0t=Temperature()t.f.bind(t.c,forward=lambdaf:(f-32)/1.8,backward=lambdac:c*1.8+32)assertt.c==0.0andt.f==32.0t.f=68.0assertt.c==20.0andt.f==68.0t.c=100.0assertt.c==100.0andt.f==212.0Note thatbind_to()only needs aforwardconverter.
Similarlybind_fromhas only abackwardconverter.Implementation and dependenciesTo achieve such a lean API we utilize three main techniques:For extending basic types withbind(),bind_to()andbind_from()methods we usecursefrom theforbiddenfruitpackage.For intercepting write access to attributes we implementBindablePropertiesasdescriptors.For finding the object and attribute name of the caller and the argument of ourbind()methods we use inspection tools from theinspectandexecutingpackages.