PDA

View Full Version : Traits Rewritten



Jeff Wheeler
July 5th, 2008, 03:27 AM
The company I work for, Enthought, has a nifty system they use in pretty much all their software called Traits (http://code.enthought.com/projects/traits/) (user manual (https://svn.enthought.com/svn/enthought/Traits/tags/enthought.traits_2.0.1b1/docs/Traits2_UM.pdf) — PDF!). It’s pretty cool, and it does some nifty stuff; they describe it as having five features:

Initialization
Validation
Delegation
Notification
Visualization

Unfortunately, I don’t really like their implementation (it’s open-source, and there’s even a Trac installation (https://svn.enthought.com/enthought/wiki/Traits) for it), because it does a lot of what I consider magic. For example, check out Pg. 2 (the page numbered two, starting from the beginning of the first chapter) of the user manual linked above: _age_changed is dynamically found and called, without the programmer ever specifying that it should be used. That doesn’t really seem Pythonic.

So, I wrote it from scratch. They won’t use my system because it doesn’t have seven years of bug-fixes and tests (although many of their actual unittests fail), but I did it more to see if I could, anyways. :P

I didn’t rewrite the visualization part, though, because I couldn’t care less about that, and I wasn’t really interested in trying to replicate that beautifully.

I’ve posted my version at my site: http://code.nokrev.com/?p=traits-rewritten.git (http://code.nokrev.com/?p=traits-rewritten.git), but I’ve reproduced some examples (based of the tests) below, so as to provide a basic feel for what it does.


from traits.base import Trait, String

class Traited(object):
x = Trait() # Value defaults to None
s = String() # Defaults to ''

if __name__ == '__main__':
t = Traited()
print t.x, t.s # => 'None '

t.x = 'Some value'
t.s = 'A string'
print t.x, t.s # => 'Some value (here, a str) A string'

Here’s an example of delegation (the child’s last name is the father’s last name). Unfortunately, I have not found a way to implement this without resorting to magic, so I still am unhappy with how this actually works (but I am open to suggestions):


from traits.base import Trait, String
from traits.delegation import Delegate

class Parent(object):
first_name = String()
last_name = String()

class Child(object):
father = Delegate(Parent)

first_name = String()
last_name = father.last_name

if __name__ == '__main__':
dad = Parent()
dad.first_name = 'Daddy'
dad.last_name = 'Wheeler'

son = Child()
son.father = dad
son.first_name = 'Jeff'

print son.last_name # => 'Wheeler'

Some 4th of July validation:


from traits.base import Trait, String, validate

class Flag(object):
color = String(default='white')

@validate(color)
def name_validator(self, trait, color):
if color.lower() not in ('red', 'white', 'blue'):
raise ValueError('Color must be "red", "white", or "blue"')

if __name__ == '__main__':
c = Flag()

try:
c.color = 'Green'
except ValueError:
print 'Could not set color to "Green"'

c.color = 'Red'
c.color = 'white'

I think you get the idea … it does some other cool stuff too, and even supports multiple inheritance. :D

Krilnon
July 5th, 2008, 07:48 AM
I came into this thread expecting some implementation of traits (http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf).


_age_changed is dynamically found and called, without the programmer ever specifying that it should be used.

That does seem fairly suspicious.

Your rewritten source seems fairly short and clean… especially compared to the pieces of their source that I looked through. However, I very rarely use Python, so I'm probably not the best judge of differences. :P

Jeff Wheeler
July 5th, 2008, 10:45 AM
I think the Zen of Python (http://www.python.org/dev/peps/pep-0020/) explains why it’s awkward pretty well:


Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

:P

Esherido
July 5th, 2008, 12:47 PM
^ I feel smart since I already knew the command to show the Zen.

TheCanadian
July 5th, 2008, 01:10 PM
I think you get the idea …
No, no I don't :P

You know too much about stuff like this.

Jeff Wheeler
July 5th, 2008, 01:19 PM
Basically, it lets you define classes with “smart” properties. They can have default values, and whenever they’re being edited, they can be validated. Listener classes can subscribe to events, too, so you can have a method that gets called whenever a property is changed or set (set is called when it’s set to the value it already was, while changed is not).

Also, delegation lets you refer to other objects and do cool stuff. :D

So, they let you do fancy properties that can be hooked into.