##// END OF EJS Templates
Use closure to prevent traitlet callbacks from firing twice.
Jonathan Frederic -
Show More
@@ -993,6 +993,8 b' class TestBind(TestCase):'
993 # Change one of the values to make sure they stay in sync.
993 # Change one of the values to make sure they stay in sync.
994 a.value = 5
994 a.value = 5
995 self.assertEqual(a.value, b.value)
995 self.assertEqual(a.value, b.value)
996 b.value = 6
997 self.assertEqual(a.value, b.value)
996
998
997 def test_bind_different(self):
999 def test_bind_different(self):
998 """Verify two traitlets of different types can be bound together using bind."""
1000 """Verify two traitlets of different types can be bound together using bind."""
@@ -1014,6 +1016,8 b' class TestBind(TestCase):'
1014 # Change one of the values to make sure they stay in sync.
1016 # Change one of the values to make sure they stay in sync.
1015 a.value = 5
1017 a.value = 5
1016 self.assertEqual(a.value, b.count)
1018 self.assertEqual(a.value, b.count)
1019 b.count = 4
1020 self.assertEqual(a.value, b.count)
1017
1021
1018 def test_unbind(self):
1022 def test_unbind(self):
1019 """Verify two binded traitlets can be unbinded."""
1023 """Verify two binded traitlets can be unbinded."""
@@ -1032,3 +1036,40 b' class TestBind(TestCase):'
1032 # Change one of the values to make sure they stay in sync.
1036 # Change one of the values to make sure they stay in sync.
1033 a.value = 5
1037 a.value = 5
1034 self.assertNotEqual(a.value, b.value)
1038 self.assertNotEqual(a.value, b.value)
1039
1040 def test_callbacks(self):
1041 """Verify two binded traitlets have their callbacks called once."""
1042
1043 # Create two simple classes with Int traitlets.
1044 class A(HasTraits):
1045 value = Int()
1046 class B(HasTraits):
1047 count = Int()
1048 a = A(value=9)
1049 b = B(count=8)
1050
1051 # Register callbacks that count.
1052 callback_count = []
1053 def a_callback(name, old, new):
1054 callback_count.append('a')
1055 a.on_trait_change(a_callback, 'value')
1056 def b_callback(name, old, new):
1057 callback_count.append('b')
1058 b.on_trait_change(b_callback, 'count')
1059
1060 # Conenct the two classes.
1061 c = bind((a, 'value'), (b, 'count'))
1062
1063 # Make sure b's count was set to a's value once.
1064 self.assertEqual(''.join(callback_count), 'b')
1065 del callback_count[:]
1066
1067 # Make sure a's value was set to b's count once.
1068 b.count = 5
1069 self.assertEqual(''.join(callback_count), 'ba')
1070 del callback_count[:]
1071
1072 # Make sure b's count was set to a's value once.
1073 a.value = 4
1074 self.assertEqual(''.join(callback_count), 'ab')
1075 del callback_count[:]
@@ -202,13 +202,15 b' class bind(object):'
202 if len(args) < 2:
202 if len(args) < 2:
203 raise TypeError('At least two traitlets must be provided.')
203 raise TypeError('At least two traitlets must be provided.')
204
204
205 self.objects = args
205 self.objects = {}
206 initial = getattr(args[0][0], args[0][1])
206 for obj,attr in args:
207 for obj,attr in args:
207 obj.on_trait_change(self._update, attr)
208 if getattr(obj, attr) != initial:
209 setattr(obj, attr, initial)
208
210
209 # Syncronize the traitlets initially.
211 callback = self._make_closure(obj,attr)
210 initial = getattr(args[0][0], args[0][1])
212 obj.on_trait_change(callback, attr)
211 self._update(args[0][1], initial, initial)
213 self.objects[(obj,attr)] = callback
212
214
213 @contextlib.contextmanager
215 @contextlib.contextmanager
214 def _busy_updating(self):
216 def _busy_updating(self):
@@ -218,16 +220,23 b' class bind(object):'
218 finally:
220 finally:
219 self.updating = False
221 self.updating = False
220
222
221 def _update(self, name, old, new):
223 def _make_closure(self, sending_obj, sending_attr):
224 def update(name, old, new):
225 self._update(sending_obj, sending_attr, new)
226 return update
227
228 def _update(self, sending_obj, sending_attr, new):
222 if self.updating:
229 if self.updating:
223 return
230 return
224 with self._busy_updating():
231 with self._busy_updating():
225 for obj,attr in self.objects:
232 for obj,attr in self.objects.keys():
226 setattr(obj, attr, new)
233 if obj is not sending_obj or attr != sending_attr:
234 setattr(obj, attr, new)
227
235
228 def unbind(self):
236 def unbind(self):
229 for obj,attr in self.objects:
237 for key, callback in self.objects.items():
230 obj.on_trait_change(self._update, attr, remove=True)
238 (obj,attr) = key
239 obj.on_trait_change(callback, attr, remove=True)
231
240
232 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
233 # Base TraitType for all traits
242 # Base TraitType for all traits
General Comments 0
You need to be logged in to leave comments. Login now