enthought / traits

Observable typed attributes for Python classes
Other
432 stars 85 forks source link

Instance(..., allow_none=False) is not typing-friendly #1673

Open mdickinson opened 2 years ago

mdickinson commented 2 years ago

Given an Instance trait declaration that uses allow_none=False, for example as in:

class A(HasTraits):
    foo = Instance(SomeClass, allow_none=False)

it would be useful for typing tools to be able to deduce that if a is an instance of A then a.foo is an instance of SomeClass, and in particular is not None.

There are currently two problems with this:

  1. there's no obvious way to indicate to the typing machinery that Instance(...) can't be None when allow_none=False
  2. it's not actually true that allow_none=False means that a.foo can't be None: if no explicit default is given, and there's no _foo_default method, then the default for a.foo is None despite the allow_none=False.

The two problems are largely orthogonal, so perhaps this should be two issues.

For the first issue, one possible fix we might consider would be to introduce a trait type factory class StrictInstance, something like (with code stolen from @corranwebster):

def StrictInstance(klass, factory=None, args=(), kwargs={}, **metadata):
    return Instance(
        klass=klass,
        factory=factory,
        args=args,
        kwargs=kwargs,
        allow_none=False,
        **metadata,
    )

Then the type hints for StrictInstance can communicate that StrictInstance(SomeClass) always stores a non-None value.

For the second issue, in almost all cases in practice the intended meaning of allow_none=False ought to be "the value of this trait is never None"; we could consider changing the behaviour so that accessing the trait value before setting it raised an exception instead of producing None. This would likely break existing code, so would need to be done carefully, and likely only in a major release.

mdickinson commented 2 years ago

See also #1662.

corranwebster commented 2 years ago

I haven't played with this, but one other possibility is that there may be some solution using a TypeGuard.