enthought / enthought/traits

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

Open
#1,753 2 comments 0 reactions 0 assignees View on GitHub
type: bug
Dominant language
Python
Stars
462
Forks
90
PR merge metrics
No merged PRs in 30d

Description

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

```python
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=, 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:

```python
...
inst.observe(observer, "trait_added")
...
```
```
trait_names: ['trait_added', 'trait_modified']
observed: TraitChangeEvent(object=<__main__.AClass object at 0x7f2068cf1450>, name='trait_added', old=, 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?

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.