Open mdickinson opened 9 years ago
(3) any attempt to change the trait value will produce an exception.
This feature is currently possible using the Readonly
trait but it would be nice if Readonly
supported type checking.
I don't think of this problem much, as you've seen from my code, but when it comes up I go with solution (2). I encounter this more when I have a DelegatesTo
that needs the delegate in place before the traits machinery kicks in. My impression is that solution (2) is discouraged, but I do not know the reasoning behind that.
I think we could do a lot to make (2) easier. E.g. the following could do the __init__
juggling automatically. (I'm not recommending this interface, I'm just showing how easy we could make this.)
class View(HasStrictTraits):
model = Instance(Model, required=True)
rescale_factor = Float(1.0)
I think this particular issue, and more generally a lack of documentation on some corner cases and special uses of traits, are biting me quite often. I am willing to document these things as I spot them, but first I have to understand them myself.
The documentation may also need to mention that this behaviour depends on the Python versions. In particular, Python 3.7+ guarantees insertion order for dictionary such that View(model=model, rescale_factor=0.5)
will initialize model
first (no errors), whereas View(rescale_factor=0.5, model=model)
will initialize rescale_factor
first, causing the exception to occur.
Difficulties with Traits initialization order are a recurring problem, that I think have cost many of us time in the past. I'm opening this issue to serve as a place to discuss possible workarounds / solutions / documentation fixes related to this problem. Ultimately, I'd like to be able to at least add recommendations to the documentation for working around this problem, but any solution which makes Traits users less likely to fall into common traps seems worth investigating.
One common pattern is that we have
HasTraits
subclassesA
andB
, and thatB
has anInstance(A)
trait where theA
instance is expected to be provided atB
creation time, and is not expected to change for the lifetime of theB
instance. A natural example occurs whenB
represents some kind of view on a model classA
. Here's a concrete example:What happens when this code is run? Well, depending on the order in which the
View
traits are set at initialization time, you'll either get the expected output:or you'll get a traceback:
If you run the code on Python 3 (or on Python 2 under
PYTHONHASHSEED=random
), you'll see both of these behaviours occurring. Otherwise, you'll only see one. It might even be the 'correct' one, but that's fragile: any future change to Python's internal hashing algorithms could turn the 'working' code into failing code.Two possible patterns for working around this problem:
(1) Treat the
model
trait as though it could change at any moment: with this solution,None
is considered a valid and expected value forself.model
, any code that accessesself.model
should be prepared to deal with the situation whereself.model is None
appropriately, and there should be trait change handlers reacting to any change inself.model
. In the above example, it's enough to declare therescaled_intensity
trait as depending onself.model
, and to have the_get_rescaled_intensity
method explicitly check for a model ofNone
and return some dummy value in that case.(2) Insist that the
model
trait is supplied atView
creation time, and doesn't change for the lifetime of the view object. That involves adding an explicit__init__
method:For extra safety, one can raise an exception on model change:
Note that using a
_model_changed
method (instead of anon_trait_change
-decorated method) doesn't work here: that method causes the_model_default
method to be called.One option I'd like to look into is adding Traits support for this particular pattern: e.g., a piece of metadata that can be used to annotate a trait to indicate that (1) that trait must be provided at creation time, (2) that trait should be initialized before others, and (3) any attempt to change the trait value will produce an exception.