traitlets
traitlets copied to clipboard
Traitlets validates default even if overrided
To make a required trait, I thought I could use:
from traitlets import HasTraits, Unicode
class Foo(HasTraits):
bar = Unicode(default_value=None, allow_none=False, required=True)
As expected, this raises a trait error, if Foo is instantiated without arguments:
f = Foo()
But: this also raises, when bar is provided:
f = Foo(bar='test')
This error is raised:
TraitError Traceback (most recent call last)
<ipython-input-8-a25b82aa7a35> in <module>
----> 1 Foo(bar='test')
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in __new__(cls, *args, **kwargs)
956 else:
957 inst = new_meth(cls, *args, **kwargs)
--> 958 inst.setup_instance(*args, **kwargs)
959 return inst
960
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in setup_instance(self, *args, **kwargs)
984 self._trait_notifiers = {}
985 self._trait_validators = {}
--> 986 super(HasTraits, self).setup_instance(*args, **kwargs)
987
988 def __init__(self, *args, **kwargs):
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in setup_instance(self, *args, **kwargs)
975 else:
976 if isinstance(value, BaseDescriptor):
--> 977 value.instance_init(self)
978
979
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in instance_init(self, obj)
520 if (self._dynamic_default_callable(obj) is None) \
521 and (self.default_value is not Undefined):
--> 522 v = self._validate(obj, self.default_value)
523 if self.name is not None:
524 obj._trait_values[self.name] = v
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in _validate(self, obj, value)
589 return value
590 if hasattr(self, 'validate'):
--> 591 value = self.validate(obj, value)
592 if obj._cross_validation_lock is False:
593 value = self._cross_validate(obj, value)
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in validate(self, obj, value)
2052 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
2053 raise TraitError(msg.format(value, self.name, class_of(obj)))
-> 2054 self.error(obj, value)
2055
2056
/usr/lib/python3.8/site-packages/traitlets/traitlets.py in error(self, obj, value)
623 e = "The '%s' trait must be %s, but a value of %r was specified." \
624 % (self.name, self.info(), repr_type(value))
--> 625 raise TraitError(e)
626
627 def get_metadata(self, key, default=None):
TraitError: The 'bar' trait of a Foo instance must be a unicode string, but a value of None <class 'NoneType'> was specified.
There's definitely some performance benefit to be gained by reducing default value validation if it will be overwritten anyway. With that said, I think the deeper problem here is that there's not an easy way to define a trait that has no default value. The above solution could be confusing to users since it doesn't really explain that they need to specify the value upon instantiating the class.
@rmorshea
In the actual codebase, we would use something like this:
from traitlets import HasTraits, Unicode
class Foo(HasTraits):
bar = Unicode(default_value=None, allow_none=False, required=True)
def __init__(bar, **kwargs):
super().__init__(bar=bar, **kwargs)
To make that point clear.
Just noting, that the behavior with the current release of traitlets here has changed, however in a for me even more suprising way.
Now, the class instantiation works, and the error is only raised when accessing the value:
from traitlets import HasTraits, Unicode
class Foo(HasTraits):
bar = Unicode(default_value=None, allow_none=False)
foo = Foo() # does not raise, but should?
foo.bar # now it raises the "expected str" error