traits icon indicating copy to clipboard operation
traits copied to clipboard

Calling `hasattr`/`getattr` creates trait if missing.

Open felixkol opened this issue 11 months ago • 2 comments

I have observed some weird behavior when calling hasattr with a nonexistent attribute. Assume the following as a minimal example:

class AClass(ta.HasTraits):
    pass


def observer(val):
    print(f"observed: {val}")

inst = AClass()
inst.observe(observer, "*")

print(f"trait_names: {inst.trait_names()}")  
print(f"hasattr('a'): {hasattr(inst, 'a')}")
print(f"trait_names: {inst.trait_names()}")
print(f"is 'a' in dir(inst)?: {'a' in dir(inst)}")
print(f"class traits of inst: {inst.__class_traits__.keys()}")

Yields:

trait_names: ['trait_added', 'trait_modified']
observed: TraitChangeEvent(object=<__main__.AClass object at 0x7f4031f304a0>, name='trait_added', old=<undefined>, new='a')
hasattr: False
trait_names: ['trait_added', 'trait_modified', 'a']
is 'a' in dir(inst)?: True
class traits of inst: dict_keys(['trait_added', 'trait_modified', 'a'])

As one can see, after calling hasattr a new trait is added for the missing attribute 'a', it appears in the list of trait_names as well, but calling getattr now would cause an AttributeError.

Furthermore, using a different expression in the observer changes this behavior:

...
inst.observe(observer, "trait_added")
...
trait_names: ['trait_added', 'trait_modified']
observed: TraitChangeEvent(object=<__main__.AClass object at 0x7f2068cf1450>, name='trait_added', old=<undefined>, new='a')
hasattr: False
trait_names: ['trait_added', 'trait_modified']
is 'a' in dir(inst)?: False
class traits of inst: dict_keys(['trait_added', 'trait_modified', 'a'])

Now, the name of the missing attribute isn't added to the 'trait_names', but the attribute is still created as a class trait.

To my understanding, getting a value should not have such side effects, especially if getting the now appearing attribute in trait_names causes an error. hasattr and trait_names should not contradict each other, but the dependents of the observation expression makes this even less deterministic.

Am I using traits wrong at this point, is this intended behavior or is it a bug?

felixkol avatar Jul 27 '23 13:07 felixkol