traits
traits copied to clipboard
HasStrictTraits not respected in multiple inheritance
Consider the following:
class A(HasTraits):
pass
class B(HasStrictTraits):
pass
class C(A, B):
pass
class D(B, A):
pass
If we do
>>> c = C()
>>> c.a = 1
there is no error (which, given that we have inherited HasStrictTraits
, we would expect to be raised).
On the other hand:
>>> d = D()
>>> d.a = 1
---------------------------------------------------------------------------
TraitError Traceback (most recent call last)
<ipython-input-6-90b5c6ae17d6> in <module>()
----> 1 d.a = 1
TraitError: Cannot set the undefined 'a' attribute of a 'D' object.
This behaves as expected.
The expected behaviour is that if a class inherits from HasStrictTraits
, which is a subclass of HasTraits
, then it should maintain the HasStrictTraits
behaviour unless something is done to explicitly turn it off.
This issue is related to #401 and was first raised in https://github.com/enthought/pyface/issues/730 which has a more detailed analysis of the suspected root problem.
This is an analysis of the problem, but it could be wrong in detail:
HasStrictTraits
is defined by setting _ = Disallow
. This defines a prefix trait that matches anything, and which doesn't let you set a value. So when you try to set a value that doesn't match a specifically defined trait, then this prefix trait gets used. So far, so good.
HasTraits
also has a prefix trait defined implicitly here that gives the normal behaviour (ie. behaves like standard Python).
Now, when you create a new subclass of HasTraits
, traits is greedy about finding trait definitions, and so it runs the MRO in reverse order, ignoring non-HasTraits
classes, building up a dictionary of trait definitions (both for normal and prefix traits), and then stores them in its trait dictionaries. In particular, for prefix traits, this is cls.__prefix_traits__
.
So in the above example A.__prefix_traits__[''] == Python
and B.__prefix_traits__[''] == Disallow
.
But C.__prefix_traits__[''] == A.__prefix_traits__[''] == Python
, which gives the standard HasTraits
behaviour; and D.__prefix_traits__[''] == B.__prefix_traits__[''] == Disallow
and so it acts like HasStrictTraits
.
If this is indeed the root cause, then a potential solution is to track which prefix traits are defined in which classes and merge in only those traits, not the whole of the __prefix_traits__
dictionary.