6

I'm new to python - sorry if my terminology is wrong. I have a class which inherits the Enthought Traits attributes. Here is a simplified version:

from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item

class GUIThing(HasTraits):

    my_slider = Range(0.0, 0.6, 0.1)
    my_slider._high = 0.7   # works; not what I need 'coz not instance-dependent

    view = View( Item('my_slider') )

    def __init__(self, arg1):
        # Call the parent's __init__
        HasTraits.__init__(self)

        self.my_slider._high = arg1  # what I need; doesn't work

# -- Main program -----

top_range = 0.9

my_gui = GUIThing(top_range)
my_gui.configure_traits()

This simply creates a window with a slider in it, originally going from 0.0 to 0.6 with initial value 0.1. When creating an instance of GUIThing I want to vary the maximum value for the slider depending on the current top_range value. However the line

self.my_slider._high = arg1

results in

AttributeError: 'float' object has no attribute '_high'

When within __init__(), self.my_slider returns not the slider object, but the current value of the slider.

What am I doing wrong? Thanks!

Edit:

The following also doesn't work:

class GUIThing(HasTraits):

    def __init__(self, arg1):
        # Call the parent's __init__
        HasTraits.__init__(self)

        self.my_slider = Range(0.0, arg1, 0.0)

    view = View( Item('my_slider') )

That would be the direct way to do what I'm trying to do, but it results in a GUI where instead of a slider, there is a text box that reads "enthought.traits.trait_types.Range object at 0xa61946c". So the problem is that when my_slider is created within __init__() then "my_slider" comes to mean the object itself (which doesn't display properly via View); but if my_slider is created outside of __init__() then "my_slider" comes to mean the current value (a float; which prevents access to the object properties).

Not sure if this is peculiar to Traits or I just don't know how to initialise objects properly.

Pteridium
  • 101
  • 5
  • 3
    Generally, the prefixed underscore in ``_high`` implies that it's private and you shouldn't be accessing it like that. – Gareth Latty Mar 31 '12 at 13:38
  • Right, thanks. But I can't find in the docs how one would access it instead. And even if it's not the proper way - it does work outside of `__init__`. So I don't understand why it is different *within* `__init__` (also see edit to OP). – Pteridium Mar 31 '12 at 15:35
  • My guess (I'm not familiar with the library) is that the superclass's ``__init__()`` uses the ``Range``, but doesn't keep reference to it. If that is true, have you tried placing the line ``self.my_slider = Range(0.0, arg1, 0.0)`` before ``HasTraits.__init__(self)`` within ``__init__()``? – Gareth Latty Mar 31 '12 at 16:11
  • Tried it just now - it makes no difference. In fact removing `HasTraits.__init__(self)` does not seem to make a difference (I took it from some example code, but it may be superfluous here). – Pteridium Mar 31 '12 at 17:27

5 Answers5

4

Finally found the answer in a recent mailing list message.

The code below works. It seems the devil is in the details of how Range() is called: Range(my_slider_low, my_slider_hi, 0.1) does not work.

from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item

class GUIThing(HasTraits):

    my_slider_low = 0.0
    my_slider_hi = 1.0

    my_slider = Range(low='my_slider_low', high='my_slider_hi', value=0.1)  

    def __init__(self, arg1):
        self.my_slider_hi = arg1 

    view = View( Item('my_slider') )

top_range = 0.2

my_gui = GUIThing(top_range)
my_gui.configure_traits()
K.-Michael Aye
  • 5,465
  • 6
  • 44
  • 56
Pteridium
  • 101
  • 5
0

There is actually a problem in Pteridium's answer. It does work but breaks two rules/recommendations.

Firstly, you have overwritten the constructor with your own init. If you do that (and if avoidable you shouldn't according to the coding recommendations for Traits), you should call the parent constructor with something like super(GUISlider, self).init(self, **kwargs)

Secondly, the recommended initialization for HasTraits children is done during the instantiation in the constructor that takes keyword arguments. In your case the code could be like

from enthought.traits.api import HasTraits, Range
from enthought.traits.ui.api import View, Item

class GUIThing(HasTraits):

    my_slider_low = 0.0
    my_slider_hi = 1.0

    my_slider = Range(low='my_slider_low', high='my_slider_hi', value=0.1)  

    view = View( Item('my_slider') )


my_gui = GUIThing(my_slider_hi=0.4)
my_gui.configure_traits()

There you go, does what you want (I think), less code and follows the Traits. If someone can explain why we shouldn't use constructors I would like to know. Robert Kern can probably tell us.

DrSAR
  • 1,522
  • 1
  • 15
  • 36
0

I cropped down a nice example of a dynamic range from Jonathan March. This gives all the behavior that the OP wants AFAICT:

# Imports:
from traits.api import HasPrivateTraits, Float, Range, Int

from traitsui.api import View, Group, Item, Label, RangeEditor

class DynamicRangeEditor ( HasPrivateTraits ):
    """ Defines an editor for dynamic ranges (i.e. ranges whose bounds can be
        changed at run time).
    """

    # The value with the dynamic range:
    value = Float

    # This determines the low end of the range:
    low = Float(0.0)

    # This determines the high end of the range:
    high = Float(50)

    # Traits view definitions:
    traits_view = View(

        # Dynamic simple slider demo:
        Group(
            Item( 'value',
                   editor = RangeEditor( low_name    = 'low',
                                         high_name   = 'high',
                                         format      = '%.1f',
                                         label_width = 28,
                                         mode        = 'auto' )
            ),
            '_',
            Item( 'low' ),
            Item( 'high' ),
            '_',
        ),

        title     = 'Dynamic Range Editor Demonstration',
        buttons   = [ 'OK' ],
        resizable = True
    )


# Create the demo:
demo = DynamicRangeEditor()

# Run the demo (if invoked from the command line):
if __name__ == '__main__':
    demo.configure_traits()
Adam Hughes
  • 14,601
  • 12
  • 83
  • 122
0

My Gut Feeling is that you don't need to modify that class but rather extend the Range class and add the extra logic that you need to handle your specific case.

fabrizioM
  • 46,639
  • 15
  • 102
  • 119
  • Thanks, one problem is I can't find how Range works internally; it doesn't actually seem to be a class but rather a function/'type definition'... see [here](http://code.enthought.com/projects/files/ets_api/enthought.traits.traits.html)? I'm confused. – Pteridium Mar 31 '12 at 15:41
  • nah, no need to mess with Range - it has all the functionality required by the OP – DrSAR May 03 '12 at 05:31
0

You need to use the add_trait method that will enable you to create dynamically new Range traits with the values you need.

This is taken from the Advanced page of the traits user manual

from traits.api import HasTraits, Range

class GUISlider (HasTraits):

def __init__(self, eval=None, label='Value',
             trait=None, min=0.0, max=1.0,
             initial=None, **traits):
    HasTraits.__init__(self, **traits)
    if trait is None:
        if min > max:
            min, max = max, min
        if initial is None:
            initial = min
        elif not (min <= initial <= max):
            initial = [min, max][
                        abs(initial - min) >
                        abs(initial - max)]
        trait = Range(min, max, value = initial)
    self.add_trait(label, trait)
K.-Michael Aye
  • 5,465
  • 6
  • 44
  • 56
  • Thanks! I hadn't noticed that. And yes, it works, if one adds `view = View(Item('Value'))` to the class. That would not do if you wanted to make multiple GUISliders with differing labels though. – Pteridium Mar 31 '12 at 22:05