diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index 79992a2..16baab7 100755 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -129,6 +129,29 @@ class TestTraitType(TestCase): a = A() self.assertRaises(TraitError, A.tt.error, a, 10) + def test_dynamic_initializer(self): + class A(HasTraits): + x = Int(10) + def _x_default(self): + return 11 + class B(A): + x = Int(20) + class C(A): + def _x_default(self): + return 21 + + a = A() + self.assertEquals(a._trait_values, {}) + self.assertEquals(a.x, 11) + self.assertEquals(a._trait_values, {'x': 11}) + b = B() + self.assertEquals(b._trait_values, {'x': 20}) + self.assertEquals(b.x, 20) + c = C() + self.assertEquals(c._trait_values, {}) + self.assertEquals(c.x, 21) + self.assertEquals(c._trait_values, {'x': 21}) + class TestHasTraitsMeta(TestCase): diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index 39c8570..a6bf9f8 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -248,9 +248,21 @@ class TraitType(object): default values must be delayed until the parent :class:`HasTraits` class has been instantiated. """ - dv = self.get_default_value() - newdv = self._validate(obj, dv) - obj._trait_values[self.name] = newdv + # Check for a deferred initializer defined in the same class as the + # trait declaration or above. + mro = type(obj).mro() + meth_name = '_%s_default' % self.name + for cls in mro[:mro.index(self.this_class)+1]: + if meth_name in cls.__dict__: + break + else: + # We didn't find one. Do static initialization. + dv = self.get_default_value() + newdv = self._validate(obj, dv) + obj._trait_values[self.name] = newdv + return + # Complete the dynamic initialization. + self.dynamic_initializer = cls.__dict__[meth_name] def __get__(self, obj, cls=None): """Get the value of the trait by self.name for the instance. @@ -265,7 +277,19 @@ class TraitType(object): else: try: value = obj._trait_values[self.name] - except: + except KeyError: + # Check for a dynamic initializer. + if hasattr(self, 'dynamic_initializer'): + value = self.dynamic_initializer(obj) + # FIXME: Do we really validate here? + value = self._validate(obj, value) + obj._trait_values[self.name] = value + return value + else: + raise TraitError('Unexpected error in TraitType: ' + 'both default value and dynamic initializer are ' + 'absent.') + except Exception: # HasTraits should call set_default_value to populate # this. So this should never be reached. raise TraitError('Unexpected error in TraitType: ' @@ -294,6 +318,11 @@ class TraitType(object): else: return value + def set_dynamic_initializer(self, method): + """ Set the dynamic initializer method, if any. + """ + self.dynamic_initializer = method + def info(self): return self.info_text