Hisham Syed
Hisham Syed

Reputation: 115

How to specify variable-length tuple of specific type in traits?

from traits.api import HasTraits, Str, Tuple
    
class Data(HasTraits):

    values = Tuple(Str, ...)


data = Data(values=("a", "b", "c"))

Output:

TraitError: The 'values' trait of a Data instance must be a tuple of the form: (a string, an ellipsis or None), but a value of ('a', 'b', 'c') <class 'tuple'> was specified.

I am learning traits api and read the docs, I didn't a find way to pass any specific type to the variable-length tuple in traits. I am getting error! How do i solve this?

Changed values = Tuple(Str, ...) to values = Tuple(Str), Still got:

TraitError: The 'values' trait of a Data instance must be a tuple of the form: (a string), but a value of ('a', 'b', 'c') <class 'tuple'> was specified.

Upvotes: 1

Views: 620

Answers (2)

Alexandre Chabot
Alexandre Chabot

Reputation: 803

According to the Tuple docs, variable length tuples are not an option:

The default value is determined as follows:

  1. If no arguments are specified, the default value is ().
  2. If a tuple is specified as the first argument, it is the default value.
  3. If a tuple is not specified as the first argument, the default value is a tuple whose length is the length of the argument list, and whose values are the default values for the corresponding trait types.

The use case you're describing, a variable-length sequence of a single type is what you get with a List(Str), although you do not get immutability. You could fake it, by creating a Property that gets and sets tuples but stores a List under the cover:

from traits.api import HasTraits, List, Str, Tuple, Property

class Foo(HasTraits):
    t_prop = Property(Tuple)
    _data = List(Str)
    
    def _get_t_prop(self):
        return tuple(self._data)
    
    def _set_t_prop(self, value):
        self._data = list(value)


foo = Foo()
foo.t_prop = ('a',)
print(foo.t_prop)
foo.t_prop = ('a', 'b')
print(foo.t_prop)
foo.t_prop = ('a', 'b', 1)
print(foo.t_prop)

This produces the output below. It's not quite right because the error message is triggered by the List validator, not the Tuple's.

('a',)
('a', 'b')
Traceback (most recent call last):
  ...
traits.trait_errors.TraitError: Each element of the '_data' trait of a Foo instance must be a string, but a value of 1 <class 'int'> was specified.

You could validate the Tuple type with something like the following, but this starts feeling a little icky:

def _set_t_prop(self, value):
        validator = Tuple((Str, ) * len(value))
        validator.validate(self, "t_prop", value)
        self._data = list(value)

The generated output is:

('a',)
('a', 'b')
traits.trait_errors.TraitError: The 't_prop' trait of a Foo instance must be a tuple of the form: (a string, a string, a string), but a value of ('a', 'b', 1) <class 'tuple'> was specified.

Upvotes: 1

Tim D
Tim D

Reputation: 1743

I can take a stab at this, although my Traits-fu is not what it once was.

You're hoping that Traits will validate a Tuple trait in the same way it validates List traits, and unfortunately that is the rub. The contents of Tuples are not validated. I think this is because Lists are mutable and Tuples are immutable, so there's an expectation that the values of a List will change (and thus need validation) and that the values of a Tuple will not, thus the Tuple trait does not carry the weight of the validation mechanism.

With that in mind, specifying values = Tuple(Str, ...) doesn't mean what you think it would mean based on how list_values = List(Str) behaves.

Consider these two classes:

from traits.api import HasTraits, List, Str, Tuple

class Foo(HasTraits):
    t_values = Tuple(Str)

class Bar(HasTraits):
    l_values = List(Str)

f = Foo(t_values=(1,2,3))
b = Bar(l_values=[1,2,3])

They should both trigger validation errors. Note the difference in errors that you get for each one:

TraitError: The 't_values' trait of a Foo instance must be a tuple of the form: (a string), but a value of (1, 2, 3) <class 'tuple'> was specified.

Traits expects t_values to be "a tuple of the form: (a string)"

TraitError: Each element of the 'l_values' trait of a Bar instance must be a string, but a value of 1 <class 'int'> was specified.

This is the normal validation error you expect when specifying the wrong type.

Upvotes: 0

Related Questions