0

I need to update the dictionary of a mapped trait some time after initial trait creation. How do I do this? The following code:

from traits.api import (HasTraits, Trait)

class bar(HasTraits):
    zap = Trait("None", {"None": None})

    def __init__(self):
        # In reality, determined programmatically at runtime.
        add_dict_entries = {"One": 1}
        new_dict = {"None": None}
        new_dict.update(add_dict_entries)
        self.zap = Trait("None", new_dict)

theBar = bar()

yields:

Traceback (most recent call last):
  File "tst_mapped_trait.py", line 13, in <module>
    theBar = bar()
  File "tst_mapped_trait.py", line 11, in __init__
    self.zap = Trait("None", new_dict)
  File "C:\Users\dbanas\Anaconda3\envs\pybert-dev\lib\site-packages\traits\trait_handlers.py", line 236, in error
    object, name, self.full_info(object, name, value), value
traits.trait_errors.TraitError: The 'zap' trait of a bar instance must be 'None', but a value of <traits.traits.CTrait object at 0x00000000034AA9E8> <class 'traits.traits.CTrait'> was specified.```

dbanas
  • 1,707
  • 14
  • 24

3 Answers3

0

Okay, the following code worked:

from traits.api import (HasTraits, Trait)

class bar(HasTraits):
    zap = Trait("None", {"None": None})

    def __init__(self):
        # In reality, determined programmatically at runtime.
        add_dict_entries = {"One": 1}
        new_dict = {"None": None}
        new_dict.update(add_dict_entries)
        # self.zap = Trait("None", new_dict)
        # self.zap.update(new_dict)
        # self.trait_setq(zap=Trait("None", new_dict))
        self.remove_trait("zap")
        self.add_trait("zap", Trait("None", new_dict))

theBar = bar()

Note: The commented out lines are things I tried, which did not work.

dbanas
  • 1,707
  • 14
  • 24
0

I'm not sure I understand what you're after, but I can make a few recommendations:

  1. Either is a good choice here if you allow both None and Dict.
  2. Use dynamic initialization to create a value for a trait at runtime. It's preferred to using an __init__ method.
  3. If you really need an __init__ method, you must call super inside of it for Traits to work properly, e.g. `super()init(*args, **kwargs)

Here's a version of your code that works and I think solves your problem.

from traits.api import (HasTraits, Either, Dict)

class bar(HasTraits):
    zap = Either(None, Dict)

    def _zap_default(self):
        add_dict_entries = {"One": 1}
        new_dict = {"None": None}
        new_dict.update(add_dict_entries)
        return new_dict

theBar = bar()
print(theBar.zap)

And here's some feedback on the code that didn't work. The line self.zap = Trait("None", new_dict) below doesn't work because it tries to create a Trait object but self.zap only accepts None or Dict. My recommendation is to use trait definitions only for typing, at the class-level. Within methods, use regular Python types.

from traits.api import (HasTraits, Trait)

class bar(HasTraits):
    zap = Trait("None", {"None": None})

    def __init__(self):
        # In reality, determined programmatically at runtime.
        add_dict_entries = {"One": 1}
        new_dict = {"None": None}
        new_dict.update(add_dict_entries)
        self.zap = Trait("None", new_dict)

theBar = bar()
  • thanks for your reply! In response to your numbered points: 1. I don't want an `Either`, just a `Dict`. 2. Dynamic initialization still only allows me to define the trait contents once; I need to redefine the trait contents many times during program execution. 3. Yes, sorry, I should have included that in my example code, to be completely correct. It's not clear to me how your proposed solution solves my problem, as I need to redefine the Trait's contents many times during program execution. – dbanas Nov 19 '19 at 16:05
0

Here's second attempt at an answer given the original poster's comment

If you want the type of zap to be Dict and only Dict, then define it as such. You can also inline the initial value if it doesn't have to be computer at runtime:

>>> from traits.api import HasTraits, Dict
>>> class Bar(HasTraits):
...     zap = Dict({5: 'e'})
...
>>> bar = Bar()
>>> bar.zap
{5: 'e'}

If it needs to be computed at runtime, then use dynamic initialization to initialize the value:

>>> class Bar(HasTraits):
...     zap = Dict()
...
...     def _zap_default(self):
...         default = {}
...         default[1] = 'a'
...         return default
...
>>> bar_dynamic = Bar()
>>> bar_dynamic.zap
{1: 'a'}

Either way, the zap attribute on the Bar instance is a regular dictionary once the class has be instantiated (after bar = Bar()). You should use Trait types after instantiation, only regular Python objects. Traits is there to define and enforce types. The type() of the objects you're assigning to the typed traits (like zap here) are regular Python types.

Here's how you'd modify zap from outside of the class:

>>> bar.zap[2] = 'b'
>>> bar.zap
{5: 'e', 2: 'b'}
>>>
>>> bar_dynamic.zap[3] = 'c'
>>> bar_dynamic.zap
{1: 'a', 3: 'c'}

And now from inside the class, as a regular attribute on self:

>>> class Bar(HasTraits):
...     zap = Dict()
...
...     def _zap_default(self):
...         default = {}
...         default[1] = 'a'
...         return default
...
...     def add_pair(self, key, value):
...         self.zap[key] = value
...
>>> bar_method = Bar()
>>> bar_method.zap
{1: 'a'}
>>> bar_method.add_pair(26, 'z')
>>> bar_method.zap
{1: 'a', 26: 'z'}
  • Thanks for continuing to try and help me resolve this! I fear I have done a terrible job of clearly explaining what I want. So, I've tried again, from scratch: https://stackoverflow.com/questions/59010527/how-do-i-make-an-edit-traits-gui-item-responsive-to-changes-in-its-dependencie – dbanas Nov 23 '19 at 17:52