Item(name='my_trait') vs Item('my_trait'), use of 'object', Delegation bug?

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Item(name='my_trait') vs Item('my_trait'), use of 'object', Delegation bug?

Ryan Olf
I'm new to traits and finding myself confused about a few things even after reading the documentation. First, what is the difference between:

View(Item(name='my_trait')) and View(Item('my_trait')) ?

The two seem to be used interchangeably in the documentation examples, but I'm finding that sometimes the difference matters. For example, in this test code: 
 ----
from traits.api import *
from traitsui.api import View, Item, EnumEditor

class Other(HasTraits):
    a = List(['1','2','3','4','5'])
    
class Test(HasTraits):
    other = Instance(Other)
    a = DelegatesTo('other')

o = Other()
t = Test()
t.other = o
t.configure_traits(view=View(Item('object.other.a')))
----
This throws errors when name= is in the Item(), but works fine when name= is omitted. Also, I'm unclear on why "object" is required in the trait specifier. When is it need exactly?

Also, I want to point out a possible bug, or have someone correct me, that replacing 'object.other.a' in the final line above with just 'a' causes a string of exceptions ('TypeError: object of type 'NoneType' has no len()') to be raised upon using the Editor to add items to the list, though it seems to me that it ought to work identically.

Again, I'm new to this and trying to find my way, so I appreciate your help clearing up some of my conceptual gaps.

Ryan





_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Item(name='my_trait') vs Item('my_trait'), use of 'object', Delegation bug?

Robert Kern
On Thu, Jan 31, 2013 at 10:36 AM, Ryan Olf <[hidden email]> wrote:

> I'm new to traits and finding myself confused about a few things even after
> reading the documentation. First, what is the difference between:
>
> View(Item(name='my_trait')) and View(Item('my_trait')) ?
>
> The two seem to be used interchangeably in the documentation examples, but
> I'm finding that sometimes the difference matters. For example, in this test
> code:
>  ----
> from traits.api import *
> from traitsui.api import View, Item, EnumEditor
>
> class Other(HasTraits):
>     a = List(['1','2','3','4','5'])
>
> class Test(HasTraits):
>     other = Instance(Other)
>     a = DelegatesTo('other')
>
> o = Other()
> t = Test()
> t.other = o
> t.configure_traits(view=View(Item('object.other.a')))
> ----
> This throws errors when name= is in the Item(), but works fine when name= is
> omitted. Also, I'm unclear on why "object" is required in the trait
> specifier. When is it need exactly?

Whenever you have a dot to reach more than one level deep. This
feature was added relatively late in Traits UI's development. Traits
UI Views operate on a small namespace called the context. This context
usually has 'object' as the object being viewed, but also 'handler'
for the Handler (which is often implicit). This context is provided by
the .trait_context() method and can be customized. For example, this
is how the Controller class works. The Controller is a Handler class
wraps a model object. You define the View and call
.configure_traits()/.edit_traits() on the Controller rather than the
model. The context the Controller provides names itself as the
'handler' and the model as the 'object'. It used to be the case that
only one '.' was allowed in an Item and the first part was the name of
the desired object in the context, usually 'object'. Dot-less names
are directed to the 'object'. This let us mix Items talking to the
model and Items that talk to the Controller, e.g.
'handler.some_gui_trait_we_dont_want_on_the_model'.

When extended trait change notifications were added to Traits, we
could now bind Items to traits several objects deep, but we still
needed to support the old 'object.foo'/'handler.bar' syntax.

This is related to why explicitly using the name='object.other.a' does
not work. The `name` attribute on the Item is actually the *last* part
of the extended syntax, the final trait name. Special parsing is done
in the constructor to break up the first argument (named `value` if
you want to use it as a keyword argument) into its component pieces.
The examples that you saw that used Item(name='a') are from the days
before the extended trait change notation where there was no real
difference.

[~/scratch]
|3> item = Item('object.other.a')

[~/scratch]
|4> item.name
'a'

[~/scratch]
|5> item.object
'object.other'

> Also, I want to point out a possible bug, or have someone correct me, that
> replacing 'object.other.a' in the final line above with just 'a' causes a
> string of exceptions ('TypeError: object of type 'NoneType' has no len()')
> to be raised upon using the Editor to add items to the list, though it seems
> to me that it ought to work identically.

It's a deep bug in Traits. When Traits goes to add a new item, it
looks at the trait that the List contains (in this case, it is an
implicit Any trait). It asks *that* trait for its default value. It
uses a function that takes both the object and the name of the List
trait to get the default value. Now comes the tricky part.

https://github.com/enthought/traits/blob/master/traits/ctraits.c#L3225

First it checks if the trait is a Property or if the trait has already
been gotten once already. It checks for the latter by seeing if it is
in the object's __dict__. Traits usually doesn't evaluate defaults
until they are asked for. Afterwards, it will put it into the
__dict__. If either of these things are true, then it will go through
a "hands-off" routine that just asks the trait for the default value
without modifying the object. However, if neither of these things are
true, then it will go through a full getattr() that will both get the
default value and set it in the __dict__ to cache it. Of course, since
the ListEditor is asking for the default value of the Any trait while
the actual named trait on the object is a DelegatesTo mapping to a
List, the default value is the wrong kind.

I believe the fix is to make _trait_default_value_for() not do the
full getattr() ever. The only other places that use
default_value_for() also cache the result in the __dict__ explicitly.

https://github.com/enthought/traits/pull/39

> Again, I'm new to this and trying to find my way, so I appreciate your help
> clearing up some of my conceptual gaps.

No problem. This is a straight-up bug. Thanks for finding it!

--
Robert Kern
Enthought
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Item(name='my_trait') vs Item('my_trait'), use of 'object', Delegation bug?

Jonathan March
Hi Ryan,

Welcome to Traits! Robert has just treated you to a virtuoso discussion of some of the deep mysteries of TraitsUI and Traits.

However the fact that such issues come up repeatedly reflects the fact that for building complex UI's, TraitsUI is showing its age and its limitations.

TraitsUI is still matchless for throwing up a quick UI reflecting a simple python model. But for more complex cases, such as we find in most real-world applications, our new UI-building library, enaml, will take you much further, with joy rather than pain. Enthought is using enaml, rather than traitsui, for new desktop UI development.

Note that Traits itself (vs TraitsUI) is still very central to our development model, and is strongly supported by enaml.

hth,
Jonathan
 


On Thu, Jan 31, 2013 at 6:31 AM, Robert Kern <[hidden email]> wrote:
On Thu, Jan 31, 2013 at 10:36 AM, Ryan Olf <[hidden email]> wrote:
> I'm new to traits and finding myself confused about a few things even after
> reading the documentation. First, what is the difference between:
>
> View(Item(name='my_trait')) and View(Item('my_trait')) ?
>
> The two seem to be used interchangeably in the documentation examples, but
> I'm finding that sometimes the difference matters. For example, in this test
> code:
>  ----
> from traits.api import *
> from traitsui.api import View, Item, EnumEditor
>
> class Other(HasTraits):
>     a = List(['1','2','3','4','5'])
>
> class Test(HasTraits):
>     other = Instance(Other)
>     a = DelegatesTo('other')
>
> o = Other()
> t = Test()
> t.other = o
> t.configure_traits(view=View(Item('object.other.a')))
> ----
> This throws errors when name= is in the Item(), but works fine when name= is
> omitted. Also, I'm unclear on why "object" is required in the trait
> specifier. When is it need exactly?

Whenever you have a dot to reach more than one level deep. This
feature was added relatively late in Traits UI's development. Traits
UI Views operate on a small namespace called the context. This context
usually has 'object' as the object being viewed, but also 'handler'
for the Handler (which is often implicit). This context is provided by
the .trait_context() method and can be customized. For example, this
is how the Controller class works. The Controller is a Handler class
wraps a model object. You define the View and call
.configure_traits()/.edit_traits() on the Controller rather than the
model. The context the Controller provides names itself as the
'handler' and the model as the 'object'. It used to be the case that
only one '.' was allowed in an Item and the first part was the name of
the desired object in the context, usually 'object'. Dot-less names
are directed to the 'object'. This let us mix Items talking to the
model and Items that talk to the Controller, e.g.
'handler.some_gui_trait_we_dont_want_on_the_model'.

When extended trait change notifications were added to Traits, we
could now bind Items to traits several objects deep, but we still
needed to support the old 'object.foo'/'handler.bar' syntax.

This is related to why explicitly using the name='object.other.a' does
not work. The `name` attribute on the Item is actually the *last* part
of the extended syntax, the final trait name. Special parsing is done
in the constructor to break up the first argument (named `value` if
you want to use it as a keyword argument) into its component pieces.
The examples that you saw that used Item(name='a') are from the days
before the extended trait change notation where there was no real
difference.

[~/scratch]
|3> item = Item('object.other.a')

[~/scratch]
|4> item.name
'a'

[~/scratch]
|5> item.object
'object.other'

> Also, I want to point out a possible bug, or have someone correct me, that
> replacing 'object.other.a' in the final line above with just 'a' causes a
> string of exceptions ('TypeError: object of type 'NoneType' has no len()')
> to be raised upon using the Editor to add items to the list, though it seems
> to me that it ought to work identically.

It's a deep bug in Traits. When Traits goes to add a new item, it
looks at the trait that the List contains (in this case, it is an
implicit Any trait). It asks *that* trait for its default value. It
uses a function that takes both the object and the name of the List
trait to get the default value. Now comes the tricky part.

https://github.com/enthought/traits/blob/master/traits/ctraits.c#L3225

First it checks if the trait is a Property or if the trait has already
been gotten once already. It checks for the latter by seeing if it is
in the object's __dict__. Traits usually doesn't evaluate defaults
until they are asked for. Afterwards, it will put it into the
__dict__. If either of these things are true, then it will go through
a "hands-off" routine that just asks the trait for the default value
without modifying the object. However, if neither of these things are
true, then it will go through a full getattr() that will both get the
default value and set it in the __dict__ to cache it. Of course, since
the ListEditor is asking for the default value of the Any trait while
the actual named trait on the object is a DelegatesTo mapping to a
List, the default value is the wrong kind.

I believe the fix is to make _trait_default_value_for() not do the
full getattr() ever. The only other places that use
default_value_for() also cache the result in the __dict__ explicitly.

https://github.com/enthought/traits/pull/39

> Again, I'm new to this and trying to find my way, so I appreciate your help
> clearing up some of my conceptual gaps.

No problem. This is a straight-up bug. Thanks for finding it!

--
Robert Kern
Enthought
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Item(name='my_trait') vs Item('my_trait'), use of 'object', Delegation bug?

_jelle_
Hi Jonathan,

> TraitsUI is still matchless for throwing up a quick UI reflecting
> a simple python model.
>But for more complex cases, such as we find in most real-world
> applications, our new UI-building library, enaml, will take you much
> further, with joy rather than pain. Enthought is using enaml,
>rather than traitsui, for new desktop UI development.

That's very interesting to hear and I'm thrilled that Enthought
is so commited to its new traits based GUI lib.
However, I'm also a bit surprised by your comment.
The reason why traitsui rocks is the wealth of well coupled
libs that come along ( pyface, envisage, apptools ).

Would you say an application like MayaVi could be ported to enaml
at this stage? IMHO I think that is still pretty far from achievable, simple
 since enaml is not  closely coupled to the rest of ETS as a whole.

That does raises 2 question:

1) Is enthought working towards that goal or will this come by osmosis
2) I think that it would be very welcome to be able to point existing
 developers to documentation that makes understandable how to
build new projects in such a way that they anticipate enaml, while making
most of the current state of ETS.
3) in other words, if this transition is coming about, a plan to the approach
 would be very welcome. right now its unclear if and how enaml and traitui
 can be used simultaneously
4) so a document on how to modernize existing apps
5) such documentation would also help enthought to channel the efforts
of its community to make such a transition come about.
6) finally, will enaml ever have a kind of pyface, or should I think of enaml as
traitsui + pyface ( in terms of functionality )?

I think some of these aspects are somewhat overdue and certainly when actively
promoting enaml over traitsui more extensive documentation about the status
of these project should be provided to make a more informed choice.

In other words, where's the wiki I'd love to pitch in ;)







_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev