I’m sure you’ve met the type. You mention some aspect of Python or Ruby or JavaScript that you really think is cool, and the other person reacts like you suggested they try the raw eel: an almost imperceptible shiver and a comment along the lines of “Yeah, but dynamic typing? Why not just fling poo at the monitor till your program’s done.”
Well at least in the case of Python, there’s another option those unhappy dogmatists who can’t bring themselves to leave the kiddie pool of strong typing: Traits.
The Traits package from Enthought introduces a special kind of type definition called a trait to the Python language. As stated in the user manual, a trait can be used for normal Python object attributes, giving the attributes some additional characteristics:
- Initialization: A trait has a default value, which is automatically set as the initial value of an attribute, before its first use in a program.
- Validation: A trait attribute is explicitly typed. The type of a trait-based attribute is evident in the code, and only values that meet a programmer-specified set of criteria (i.e., the trait definition) can be assigned to that attribute. Note that the default value need not meet the criteria defined for assignment of values. Traits 3.0 also supports defining and using abstract interfaces, as well as adapters between interfaces.
- Deferral: The value of a trait attribute can be contained either in the defining object or in another object that is deferred to by the trait.
- Notification: Setting the value of a trait attribute can notify other parts of the program that the value has changed.
- Visualization: User interfaces that allow a user to interactively modify the values of trait attributes can be automatically constructed using the traits’ definitions. This feature requires that a supported GUI toolkit be installed. However, if this feature is not used, the Traits package does not otherwise require GUI support. For details on the visualization features of Traits, see the Traits UI User Guide.
A class can freely mix trait-based attributes with normal Python attributes, or can opt to allow the use of only a fixed or open set of trait attributes within the class. Trait attributes defined by a class are automatically inherited by any subclass derived from the class.
Using them is pretty easy, just extend your class from HasTraits.
[python]
class OldSchool(object):
def __init__(self):
# Man, I hope no one puts something other than a string in name!
self.name = ”
from enthought.traits.api import HasTraits, String
class NewHotness(HasTraits):
# Just try setting name to an int, hippie!
name = String()
[/python]
If you try to set a trait to any old type, you get a TraitError exception.
[text]
>>> from enthought.traits.api import HasTraits, String
>>> class NewHotness(HasTraits):
… name = String()
…
>>> nh = NewHotness()
>>> nh.name
”
>>> nh.name = ‘A string’
>>> nh.name
‘A string’
>>> nh.name = object()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Tools\Python26\lib\site-packages\enthought\traits\trait_types.py", line 720, in validate
return getattr( self, self._validate )( object, name, value )
File "C:\Tools\Python26\lib\site-packages\enthought\traits\trait_types.py", line 744, in validate_str
self.error( object, name, value )
File "C:\Tools\Python26\lib\site-packages\enthought\traits\trait_handlers.py", line 175, in error value )
enthought.traits.trait_errors.TraitError: The ‘name’ trait of a NewHotness instance must be a string, but a value of <object object at 0x01D315F8> <type ‘object’> was specified.
[/text]
The predefined traits allow you to set maximum and minimum values, default values, as well support sensible type conversions. For example, if I set name to a float or an integer, the String trait will convert the value to a string.
[python]
>>> nh.name = 1.23
>>> nh.name
‘1.23’
[/python]
For the most part, traits are compatible with the other packages that make Python such a nice language to program in. For example, you can pickle classes that use traits or stuff them into an object database if you want.
[python]
try:
from cPickle import loads, dumps
except:
from pickle import loads, dumps
from enthought.traits.api import HasTraits, String
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
import transaction
class MyClass(HasTraits):
name = String()
def test_pickle():
m = MyClass()
m.name = ‘Bob’
p = dumps(m)
n = loads(p)
print ‘SAME:’, m is n
print ‘EQUAL:’, m.name == n.name
print ‘m.name =’, m.name
print ‘n.name =’, n.name
def test_write_zodb():
storage = FileStorage(‘TestData.fs’)
db = DB(storage)
connection = db.open()
root = connection.root()
m = MyClass()
m.name = ‘Frank’
root[‘Frank’] = m
transaction.commit()
print root.keys()
db.close()
def test_read_zodb():
storage = FileStorage(‘TestData.fs’)
db = DB(storage)
connection = db.open()
root = connection.root()
m = root[‘Frank’]
print "Name =", m.name
db.close()
if __name__ == ‘__main__’:
test_pickle()
test_write_zodb()
test_read_zodb()
[/python]
Running the snippet above results in the following output:
[text]
C:\Tools\Python26\python.exe "C:/Tools/Python/TraitTests/TraitTester.py"
SAME: False
EQUAL: True
m.name = Bob
n.name = Bob
[‘Frank’]
Name = Frank
[/text]
There’s a lot more to traits than what I’ve described here. You can grab the package from PyPI by downloading directly or via easy_install.
Leave a Reply