##// 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 993 # Change one of the values to make sure they stay in sync.
994 994 a.value = 5
995 995 self.assertEqual(a.value, b.value)
996 b.value = 6
997 self.assertEqual(a.value, b.value)
996 998
997 999 def test_bind_different(self):
998 1000 """Verify two traitlets of different types can be bound together using bind."""
@@ -1014,6 +1016,8 b' class TestBind(TestCase):'
1014 1016 # Change one of the values to make sure they stay in sync.
1015 1017 a.value = 5
1016 1018 self.assertEqual(a.value, b.count)
1019 b.count = 4
1020 self.assertEqual(a.value, b.count)
1017 1021
1018 1022 def test_unbind(self):
1019 1023 """Verify two binded traitlets can be unbinded."""
@@ -1032,3 +1036,40 b' class TestBind(TestCase):'
1032 1036 # Change one of the values to make sure they stay in sync.
1033 1037 a.value = 5
1034 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 202 if len(args) < 2:
203 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 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.
210 initial = getattr(args[0][0], args[0][1])
211 self._update(args[0][1], initial, initial)
211 callback = self._make_closure(obj,attr)
212 obj.on_trait_change(callback, attr)
213 self.objects[(obj,attr)] = callback
212 214
213 215 @contextlib.contextmanager
214 216 def _busy_updating(self):
@@ -218,16 +220,23 b' class bind(object):'
218 220 finally:
219 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 229 if self.updating:
223 230 return
224 231 with self._busy_updating():
225 for obj,attr in self.objects:
232 for obj,attr in self.objects.keys():
233 if obj is not sending_obj or attr != sending_attr:
226 234 setattr(obj, attr, new)
227 235
228 236 def unbind(self):
229 for obj,attr in self.objects:
230 obj.on_trait_change(self._update, attr, remove=True)
237 for key, callback in self.objects.items():
238 (obj,attr) = key
239 obj.on_trait_change(callback, attr, remove=True)
231 240
232 241 #-----------------------------------------------------------------------------
233 242 # Base TraitType for all traits
General Comments 0
You need to be logged in to leave comments. Login now