Traits User Manual
Previous: 2 Defining Traits: Initialization and Validation Table of Contents  
Traits User Manual
1 Introduction
2 Defining Traits: Initialization and Validation
3 Advanced Topics

3                    Advanced Topics

The preceding sections provide enough information for you to use traits for manifestly-typed attributes, with initialization and validation. This section describes the advanced features of the Traits package, including custom trait handlers, notification, and delegation. It also delves into advanced aspects of initialization and validation.

3.1                   Custom Trait Handlers

If you need a trait that cannot be defined using the standard set of trait handling classes, you can create your own subclass of TraitHandler. The constructor (i.e., __init__ method) for your TraitHandler subclass can accept whatever additional information, if any, is needed to completely specify the trait. The constructor does not need to call the TraitHandler base class’s constructor.

The only method that a custom trait handler must implement is validate(). Refer to the Traits API Reference for details about this function. 

In many cases, it is also necessary to override one or more of the following methods in a TraitHandler subclass:

info(self)

post_setattr(self, object, name, value)

get_editor(self, trait)

3.1.1                   Example Custom Trait Handler

To illustrate the process of creating a TraitHandler subclass, the following is a definition of a trait handler that only allows positive, odd integers as legal values:

# custom_traithandler.py --- Example of a custom TraitHandler

import types

from enthought.traits.api import TraitHandler

 

class TraitOddInteger(TraitHandler):

 

    def validate(self, object, name, value):

        if ((type(value) is types.IntType) and

            (value > 0) and ((value % 2) == 1)):

            return value

        self.error(object, name, value)

 

    def info(self):

        return 'a positive odd integer'

An application could use this new trait handler to define traits such as the following:

# use_custom_th.py --- Example of using a custom TraitHandler

from enthought.traits.api import HasTraits, Trait, TraitRange

from custom_traithandler import TraitOddInteger

 

class AnOddClass(HasTraits):

    oddball = Trait(1, TraitOddInteger())

    very_odd = Trait(-1, TraitOddInteger(),

                         TraitRange(-10, -1))

The following example demonstrates why the info() method returns a phrase rather than a complete sentence:

>>> from use_custom_th import AnOddClass

>>> odd_stuff = AnOddClass()

>>> odd_stuff.very_odd = 0

Traceback (most recent call last):

  File "test.py", line 25, in ?

    odd_stuff.very_odd = 0  

  File "C:\cvsroot\traits\traits.py", line 1119, in validate

    raise TraitError, excp

traits.traits.TraitError: The 'very_odd' trait of a AnOddClass instance must be a positive odd integer or an integer in the range from -10 to -1, but a value of 0 was specified.

Note the emphasized result returned by the info() method, which is embedded in the exception generated by the invalid assignment.

3.2                   Trait Notification

When the value of an attribute changes, other parts of the program might need to be notified that the change has occurred. The Traits package makes this possible for trait attributes. This functionality lets you write programs using the same, powerful event-driven model that is used in writing user interfaces and for other problem domains.

Requesting trait attribute change notifications is done in one of two ways:

·    Statically, by writing methods using a special naming convention in the class defining the trait attribute whose change notification is to be handled.

·    Dynamically, by calling either on_trait_change() or on_trait_event() to establish (or remove) change notification handlers.

3.2.1                   Static Notification

The static approach is the most convenient, but it is not always possible to use it. Writing a static change notification handler requires that, for a class whose trait attribute changes you are interested in, you write a method with a special name on that class (or a subclass).

There are two kinds of special method names that can be used for static trait attribute change notifications. One is attribute-specific, and the other applies to all trait attributes on a class.

To notify about changes to a trait attribute named name, define a method named _name_changed() or _name_fired(). The leading underscore indicates that attribute-specific notification handlers are normally part of a class's private API. Methods named _name_fired() are normally used with traits that are events, described in Section 3.2.3, “Trait Events”.

To notify about changes to any trait attribute on a class, define a method named _anytrait_changed().

Both of these types of static trait attribute notification methods are illustrated in the following example: 

# static_notification.py --- Example of static attribute

#                            notification

from enthought.traits.api import HasTraits, Trait

 

class Person(HasTraits):

    weight_kg = Trait(0.0)

    height_m =  Trait(1.0)

    bmi = Trait(0.0)

 

    def _weight_kg_changed(self, old, new):

         print 'weight_kg changed from %s to %s ' % (old, new)

         if self.weight.kg != 0.0

             self.bmi = self.weight_kg / (self.height_m**2)

 

    def _anytrait_changed(self, name, old, new):

         print 'The %s trait changed from %s to %s ' \

                % (name, old, new)

"""

>>> bob = Person()

>>> bob.height_m = 1.75

The height_m trait changed from 1.0 to 1.75

>>> bob.weight_kg = 100.0

The weight_kg trait changed from 0.0 to 100.0

weight_kg changed from 0.0 to 100.0

The bmi trait changed from 0.0 to 32.6530612245

"""

In this example, the attribute-specific notification function is _weight_kg_changed(), which is called only when the weight_kg attribute changes. The class-specific notification handler is _anytrait_changed(), and is called when weight_kg, height_m, or bmi changes. Thus, both handlers are called when the weight_kg attribute changes. Also, the _weight_kg_changed() function modifies the bmi attribute, which causes _anytrait_changed() to be called for that attribute.

The arguments passed to the trait attribute change notification method depend on the method signature and on which type of static notification handler it is.

For an attribute specific notification handler, the method signatures supported are:

_name_changed(self)

_name_changed(self, new)

_name_changed(self, old, new)

_name_changed(self, name, old, new)

The method name can also be _name_fired(), with the same set of signatures.

In these signatures:

·    new is the new value assigned to the trait attribute.

·    old is the old value assigned to the trait attribute.

·    name is the name of the trait attribute.

You can choose whatever method signature from this list is most convenient to use.

In the case of a non-attribute specific handler, the method signatures supported are:

_anytrait_changed(self)

_anytrait_changed(self, name)

_anytrait_changed(self, name, new)

_anytrait_changed(self, name, old, new)

The meanings for name, new, and old are the same as for attribute-specific notification functions.

3.2.2                   Dynamic Notification

In cases where a notification handler cannot be defined on the class (or a subclass) whose trait attribute changes are to be monitored, you can use dynamic notification instead. In this case, you define a handler method, and then invoke the on_trait_change() or on_trait_event() method register that handler with the object being monitored. The handler registration methods have the following signatures:

obj.on_trait_change(handler, name=None, remove=False)

obj.on_trait_event(handler, name=None, remove=False)

The handler parameter specifies the function or bound method to be called whenever the name attribute of obj is modified. If name is None or omitted, handler is called whenever any trait attribute of obj is modified. If remove is True (or non-zero), then handler will no longer be called when the name (or any) trait attribute of obj is modified. In other words, it causes the handler to be “unhooked” from the event.

Setting up a dynamic trait attribute change notification handler is illustrated in the following example: 

# dynamic_notification --- Example of dynamic notification

from enthought.traits.api import Float, HasTraits, Trait

 

class Part (HasTraits):

  cost = Trait(0.0)

 

class Widget (HasTraits):

  part1 = Trait(Part)

  part2 = Trait(Part)

  cost  = Float(0.0)

 

  def __init__(self):

    self.part1 = Part()

    self.part2 = Part()

    self.part1.on_trait_change(self.update_cost, 'cost')

    self.part2.on_trait_change(self.update_cost, 'cost')

 

  def update_cost(self):

    self.cost = self.part1.cost + self.part2.cost

   

"""

>>> w = Widget()

>>> w.part1.cost = 2.25

>>> w.part2.cost = 5.31

>>> print w.cost

7.56

"""

In this example, the Widget constructor sets up a dynamic trait attribute change notification so that its update_cost() method is called whenever the cost attribute of either its part1 or part2 attributes is modified.

The handler passed to on_trait_change() or on_trait_event() can have any one of the following signatures:

handler()

handler(new)

handler(name, new)

handler(object, name, new)

handler(object, name, old, new)

Unlike the static trait attribute change notification handlers, the signature of a dynamic handler does not depend upon whether the handler is attribute-specific.

3.2.3                   Trait Events

The Traits package defines a special type of trait called an event. Events are created using the Event() function, which accepts all of the same arguments as the Trait() function.

There are two major differences between a normal trait and an event:

·    All notification handlers associated with an event are called whenever any value is assigned to the event. A normal trait attribute only calls its associated notification handlers when the previous value of the attribute is different from the new value being assigned to it.

·    An event does not use any storage, and in fact does not store the values assigned to it. Any value assigned to an event is reported as the new value to all associated notification handlers, and then immediately discarded. Because events do not retain a value, the old argument to a notification handler associated with an event is always the special Undefined object (see Section 3.2.4). Similarly, attempting to read the value of an event results in a TraitError exception, because an event has no value.

As an example of an event, consider: 

# event.py --- Example of trait event

from enthought.traits.api import Event, HasTraits, List, Tuple

 

point_2d = Tuple(0, 0)

 

class Line2D(HasTraits):

    points = List(point_2d)

    line_color = RGBAColor('black')

    updated = Event

   

    def redraw():

        pass # Not implemented for this example

 

    def _points_changed():

        self.updated = True

       

    def _updated_fired():

        self.redraw()

In support of the use of events, the Traits package understands attribute-specific notification handlers with names of the form _name_fired(), with signatures identical to the _name_changed() functions. In fact, the Traits package does not check whether the trait attributes that _name_fired() handlers are applied to are actually events. The function names are simply synonyms for programmer convenience.

Similarly, a function named on_trait_event() can be used as a synonym for on_trait_change() for dynamic notification.

3.2.4                   Undefined Object

Python defines a special, singleton object called None. The Traits package introduces an additional special, singleton object called Undefined.

The Undefined object is used to indicate that a trait attribute has not yet had a value set (i.e., its value is undefined). Undefined is used instead of None, because None is often used for other meanings, such as that the value is not used. In particular, when a trait attribute is first assigned a value and its associated trait notification handlers are called, Undefined is passed as the value of the old parameter to each handler, to indicate that the attribute previously had no value. Similarly, the value of a trait event is always Undefined.

3.3                   Trait Delegation

One of the advanced capabilities of the Traits package is its ability to delegate the definition and default value of a trait attribute to another object than the one the attribute is defined on. This has many applications, especially in cases where objects are logically contained within other objects and may wish to inherit or derive some attributes from the object they are contained in or associated with. Delegation leverages the common “has-a” relationship between objects, rather than the “is-a” relationship that class inheritance provides.

Trait attributes based on delegation are defined using the Delegate() function, rather than the Trait() function.

3.3.1                   Delegate() Function

The signature of the Delegate function is:

Delegate(delegate, prefix='', modify=False)

The delegate parameter is a string that specifies the name of the object attribute that refers to the trait’s delegate. The current value of the trait attribute defined by delegate is used as the delegate whenever the delegate is needed. Therefore, if the attribute referenced by delegate changes its value, the delegation also changes to use the object referenced by the new value. The prefix and modify parameters to the Delegate() function specify additional information about how to do the delegation.

If prefix is the empty string or omitted, the delegation is to an attribute of the delegate object with the same name as the trait defined by the Delegate() function. Consider the following example:

# delegate.py --- Example of trait delegation

from enthought.traits.api import Delegate, HasTraits, Str, Trait

 

class Parent(HasTraits):

    first_name = Str

    last_name  = Str

 

class Child(HasTraits):

    first_name = Trait('')

    last_name  = Delegate('father')

    father     = Trait(Parent)

    mother     = Trait(Parent)

"""

>>> tony  = Parent(first_name='Anthony', last_name='Jones')

>>> alice = Parent(first_name='Alice', last_name='Smith')

>>> sally = Child( first_name='Sally', father=tony, mother=alice)

>>> print sally.last_name

Jones

>>> sally.last_name = 'Smith'

>>> sally.last_name = sally.mother # ERR: string expected

Traceback (most recent call last):

  File "<stdin>", line 1, in ?

  File "c:\wrk\src\lib\enthought\traits\trait_handlers.py", line

90, in error

    raise TraitError, ( object, name, self.info(), value )

enthought.traits.trait_errors.TraitError: The 'last_name' trait of a Child instance must be a value of type 'str', but a value of <__main__.Parent object at 0x009DD6F0> was specified.

"""

A Child object delegates its last_name attribute to its father object’s last_name attribute. Because the prefix parameter was not specified in the Delegate() function used to define the Child class’s last_name attribute, the attribute name of the delegate is the same as the original attribute name. Thus, by default, the last_name of a Child is the same as the last_name of its father.

Note, however, that once a value is explicitly assigned to the last_name attribute of a Child, it takes on the explicitly assigned value. This behavior is illustrated in the example when Sally’s last name is set to be the same as her mother’s last name. However, delegation still affects the type of values that can be assigned to the last_name attribute, as illustrated in the example by the attempt to assign Sally’s mother (an object) as Sally’s last name. The last_name attribute delegates the assignment to the father trait, whose last_name attribute specifies that the only legal values are strings.

3.3.1.1                   Prefix Keyword

When the prefix parameter to the Delegate() function is a non-empty string, the rule for performing trait attribute look-up in the delegated to object is modified, with the modification depending on the format of the prefix string:

·    If prefix is a valid Python attribute name, then the original attribute name is replaced by prefix when performing the delegate object attribute look-up.

·    If prefix ends with an asterisk ('*'), and is longer than one character, then prefix, minus the trailing asterisk, is added to the front of the original attribute name when performing the delegate object trait look-up.

·    If prefix is equal to a single asterisk ('*'), the value of the object class’s __prefix__ attribute is added to the front of the original attribute name when performing the delegate object trait look-up.

Each of these three possibilities is illustrated in the following example:

# delegate_prefix.py --- Examples of Delegate() prefix parameter

from enthought.traits.api import \

    Delegate, Float, HasTraits, Str, Trait

 

class Parent (HasTraits):

    first_name = Str

    family_name = ''

    favorite_first_name = Str

    child_allowance = Float(1.00)

 

class Child (HasTraits):

    __prefix__ = 'child_'

    first_name = Delegate('mother', 'favorite_*')

    last_name  = Delegate('father', 'family_name')

    allowance  = Delegate('father', '*')

    father     = Trait(Parent)

    mother     = Trait(Parent)

In this example, instances of the Child class have three delegated trait attributes:

·    first_name, which delegates to the favorite_first_name attribute of its mother attribute.

·    last_name, which delegates to the family_name attribute of its father attribute.

·    allowance, which delegates to the child_allowance attribute of its father attribute.

3.3.1.2                   Modify Keyword

The final form of delegation occurs when the modify parameter to the Delegate() function is True. In this case, the attribute delegates to the attribute specified by the delegate and prefix parameters as before, but any changes to the attribute are made to the delegate object’s attribute value, not to the object delegating the attribute. This form is useful for implementing a proxy design pattern, where the object using delegation is really a proxy for another object. 

Note that when using delegation, the attribute being delegated to (such as family_name in the preceeding example) need not be defined by a trait. That is, the attribute that is delegated-to can be a standard Python attribute.

3.4                   Initialization and Validation Revisited

The following sections present advanced topics related to the initialization and validation features of the Traits package.

·    Dynamic initialization

·    Overriding default values

·    Reusing trait definitions

·    Trait attribute definition strategies

·    Type-checked methods

3.4.1                   Dynamic Initialization

When you use the Trait() function or other trait factory functions to define traits, you specify their default values statically. You can also define a method that dynamically initializes a trait attribute the first time that the attribute value is accessed. To do this, you define a method with the following signature:

 _name_default(self)

This method initializes the name trait attribute, returning its initial value. The method overrides any default value specified in the trait definition.

3.4.2                   Overriding Default Values in a Subclass

Often, a subclass must override a trait attribute in a parent class by providing a different default value. You can specify a new default value without completely re-specifying the trait definition for the attribute. For example:

# override_default.py -- Example of overriding a default value for

#                        a trait attribute in a subclass

from enthought.traits.api import HasTraits, Range

 

class Employee(HasTraits):

    name = Str

    salary_grade = Range(value=1, low=1, high=10)

   

class Manager(Employee):

    salary_grade = 5

In this example, the salary_grade of the Employee class is a range from 1 to 10, with a default value of 1. In the Manager subclass, the default value of salary_grade is 5, but it is still a range as defined in the Employee class.

3.4.3                   Reusing Trait Definitions

As mentioned in Section 2, “Defining Traits: Initialization and Validation”, in most cases, traits are defined in-line in attribute definitions, but they can also be defined independently. A trait definition only describes the characteristics of a trait, and not the current value of a trait attribute, so it can be used in the definition of any number of attributes. For example:

# trait_reuse.py --- Example of reusing trait definitions

from enthought.traits.api import \

    HasTraits, Range, Trait, TraitRange

 

coefficient = Trait(0.0, TraitRange(-1.0, 1.0))

 

class quadratic(HasTraits):

    c2 = coefficient

    c1 = coefficient

    c0 = coefficient

    x  = Range(-100.0, 100.0, 0.0)

In this example, a trait named coefficient is defined externally to the class quadratic, which references coefficient in the definitions of its trait attributes c2, c1, and c0. Each of these attributes has a unique value, but they all use the same trait definition to determine whether a value assigned to them is valid.

3.4.4                   Trait Attribute Definition Strategies

In the preceding examples in this guide, all trait attribute definitions have bound a single object attribute to a specified trait definition. This is known as explicit trait attribute definition. The Traits package supports other strategies for defining trait attributes. You can associate a category of attributes with a particular trait definition, using the trait attribute name wildcard. You can also dynamically create trait attributes that are specific to an instance, using the add_trait() method, rather than defined on a class. These strategies are described in the following sections.

3.4.4.1                   Trait Attribute Name Wildcard

The Traits package enables you to define a category of trait attributes associated with a particular trait definition, by including an underscore ('_') as a wildcard at the end of a trait attribute name. For example:

# temp_wildcard.py --- Example of using a wildcard with a Trait

#                      attribute name

from enthought.traits.api import Any, HasTraits

 

class Person(HasTraits):

    temp_ = Any

This example defines a class Person, with a category of attributes that have names beginning with 'temp', and that are defined by the Any trait. Thus, any part of the program that uses a Person instance can reference attributes such as tempCount, temp_name, or temp_whatever, without having to explicitly declare these trait attributes. Each such attribute has None as the initial value and allows assignment of any value (because it is based on the Any trait).

You can even give all object attributes a default trait definition, by specifying only the wildcard character for the attribute name:

# all_wildcard.py --- Example of trait attribute wildcard rules

from enthought.traits.api import Any, HasTraits

 

class Person(HasTraits):

    _ = Any

   

In this case, all Person instance attributes can be created on the fly and are defined by the Any trait.

3.4.4.1.1                    Wildcard Rules

When using wildcard characters in trait attribute names, the following rules are used to determine what trait definition governs an attribute:

1.     If an attribute name exactly matches a name without a wildcard character, that definition applies.

2.     Otherwise, if an attribute name matches one or more names with wildcard characters, the definition with the longest name applies.

Note that all possible attribute names are covered by one of these two rules. The base HasTraits class implicitly contains the attribute definition _ = Python. This rule guarantees that, by default, all attributes have standard Python language semantics.

These rules are demonstrated by the following example:

# wildcard_rules.py --- Example of trait attribute wildcard rules

from enthought.traits.api import Any, HasTraits, Int, Python

 

class Person(HasTraits):

    temp_count = Int(-1)

    temp_      = Any

    _          = Python

   

In this example, the Person class has a temp_count attribute, which must be an integer and which has an initial value of -1. Any other attribute with a name starting with ‘temp’ has an initial value of None and allows any value to be assigned. All other object attributes behave like normal Python attributes (i.e., they allow any value to be assigned, but they must have a value assigned to them before their first reference).

3.4.4.1.2                    Disallow Object

The singleton object Disallow can be used with wildcards to disallow all attributes that are not explicitly defined. For example:

# disallow.py --- Example of using Disallow with wildcards

from enthought.traits.api import \

    Disallow, Float, HasTraits, Int, Str

 

class Person (HasTraits):

    name   = Str

    age    = Int

    weight = Float

    _      = Disallow

   

In this example, a Person instance has three trait attributes:

·    name—Must be a string; its initial value is ''.

·    age—Must be an integer; its initial value is 0.

·    weight—Must be a float; its initial value is 0.0.

All other object attributes are explicitly disallowed. That is, any attempt to read or set any object attribute other than name, age, or weight causes an exception.

3.4.4.1.3                    HasTraits Subclasses

Because the HasTraits class implicitly contains the attribute definition _ = Python, subclasses of HasTraits by default have very standard Python attribute behavior for any attribute not explicitly defined as a trait attribute. However, the wildcard trait attribute definition rules make it easy to create subclasses of HasTraits with very non-standard attribute behavior. Two such subclasses are predefined in the Traits package: HasStrictTraits and HasPrivateTraits.

3.4.4.1.4            HasStrictTraits

This class guarantees that accessing any object attribute that does not have an explicit or wildcard trait definition results in an exception. This can be useful in cases where a more rigorous software engineering approach is employed than is typical for Python programs. It also helps prevent typos and spelling mistakes in attribute names from going unnoticed; a misspelled attribute name typically causes an exception. The definition of HasStrictTraits is the following:

  class HasStrictTraits(HasTraits):

      _ = Disallow

HasStrictTraits can be used to create type-checked data structures, as in the following example:

   class TreeNode(HasStrictTraits):

       left = This

       right = This

       value = Str

This example defines a TreeNode class that has three attributes: left, right, and value. The left and right attributes can only be references to other instances of TreeNode (or subclasses), while the value attribute must be a string. Attempting to set other types of values generates an exception, as does attempting to set an attribute that is not one of the three defined attributes. In essence, TreeNode behaves like a type-checked data structure.

3.4.4.1.5            HasPrivateTraits

This class is similar to HasStrictTraits, but allows attributes beginning with '_' to have an initial value of None, and to not be type-checked. This is useful in cases where a class needs private attributes, which are not part of the class's public API, to keep track of internal object state. Such attributes do not need to be type-checked because they are only manipulated by the (presumably correct) methods of the class itself. The definition of HasPrivateTraits is the following:

  class HasPrivateTraits(HasTraits):

      __ = Any

      _  = Disallow

These subclasses of HasTraits are provided as a convenience, and their use is completely optional. However, they do illustrate how easy it is to create subclasses with customized default attribute behavior if desired.

3.4.4.2                   Per-Object Trait Attributes

The Traits package allows you to define dynamic trait attributes that are object-, rather than class-, specific. This is accomplished using the add_trait() method of the HasTraits class:

object.add_trait(name, trait)

For example:

# object_trait_attrs.py --- Example of per-object trait attributes

from enthought.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)

       

This example creates a GUISlider class, whose __init__() method can accept a string label and either a trait definition or minimum, maximum, and initial values. If no trait definition is specified, one is constructed based on the max and min values. A trait attribute whose name is the value of label is added to the object, using the trait definition (whether specified or constructed). Thus, the label trait attribute on the GUISlider object is determined by the calling code, and added in the __init__() method using add_trait().

You can require that add_trait() must be used in order to add attributes to a class, by deriving the class from HasStrictTraits (see Section 3.4.4.1.4). When a class inherits from HasStrictTraits, the program cannot create a new attribute (either a trait attribute or a regular attribute) simply by assigning to it, as is normally the case in Python. In this case, add_trait() is the only way to create a new attribute for the class outside of the class definition.

3.4.5                   Type-Checked Methods

In addition to providing type-checked attributes, the Traits package also provides the ability to create type-checked methods.

A type-checked method is created by writing a normal method definition within a class, preceded by a method() signature function call, as shown in the following example:

# type_checked_methods.py --- Example of traits-based method type

#                             checking

from enthought.traits.api import HasTraits, method, Tuple

 

Color = Tuple(int, int, int, int)

 

class Palette(HasTraits):

 

    method(Color, color1=Color, color2=Color)

    def blend (self, color1, color2):

        return ((color1[0] + color2[0]) / 2,

                (color1[1] + color2[1]) / 2,

                (color1[2] + color2[2]) / 2,

                (color1[3] + color2[3]) / 2 )

    method(Color, Color, Color)

    def max (self, color1, color2):

        return (max( color1[0], color2[0]),

                max( color1[1], color2[1]),

                max( color1[2], color2[2]),

                max( color1[3], color2[3]) )

In this example, Color is defined to be a trait that accepts tuples of four integer values. The method() signature function appearing before the definition of the blend() method ensures that the two arguments to blend() both match the Color trait definition, as does the result returned by blend(). The method signature appearing before the max() method does exactly the same thing, but uses positional rather than keyword arguments. When

Use of the method() signature function is optional. Methods not preceded by a method() function have standard Python behavior (i.e., no type-checking of arguments or results is performed). Also, the method() function can be used in classes that do not subclass from HasTraits, because the resulting method performs the type checking directly. And finally, when the method() function is used, it must directly precede the definition of the method whose type signature it defines. (However, white space is allowed.) If it does not, a TraitError is raised.

3.5                   Useful Methods on HasTraits

The HasTraits class defines a number of methods, which are available to any class derived from it, i.e., any class that uses trait attributes. This section provides examples of a sample of these methods. Refer to the Traits API Reference for a complete list of HasTraits methods.

3.5.1                   add_trait()

This method adds a trait attribute to an object dynamically, after the object has been created. For more information, see Section 3.4.4.2, “Per-Object Trait Attributes”.

3.5.2                   clone_traits()

This method copies trait attributes from one object to another. It can copy specified attributes, all explicitly defined trait attributes, or all explicitly and implicitly defined trait attributes on the source object.

This method is useful if you want to allow a user to edit a clone of an object, so that changes are made permanent only when the user commits them. In such a case, you might clone an object and its trait attributes; allow the user to modify the clone; and then re-clone only the trait attributes back to the original object when the user commits changes.

3.5.3                   set()

This takes a list of keyword-value pairs, and sets the trait attribute corresponding to each keyword to the matching value. This shorthand is useful when a number of trait attributes need to be set on an object, or a trait attribute value needs to be set in a lambda function. For example:

person.set(name='Bill', age=27)

The statement above is equivalent to the following:

person.name = 'Bill'

person.age = 27

3.5.4                   add_class_trait()

The add_class_trait() method is a class method, while the preceding HasTraits methods are instance methods. This method is very similar to the add_trait() instance method. The difference is that adding a trait attribute by using add_class_trait() is the same as having declared the trait as part of the class definition. That is, any trait attribute added using add_class_trait() is defined in every subsequently-created instance of the class, and in any subsequently-defined subclasses of the class. In contrast, the add_trait() method adds the specified trait attribute only to the object instance it is applied to.

In addition, if the name of the trait attribute ends with a '_', then a new (or replacement) prefix rule is added to the class definition, just as if the prefix rule had been specified statically in the class definition. It is not possible to define new prefix rules using the add_trait() method.

One of the main uses of the add_class_trait() method is to add trait attribute definitions that could not be defined statically as part of the body of the class definition. This occurs, for example, when two classes with trait attributes are being defined and each class has a trait attribute that should contain a reference to the other. For the class that occurs first in lexical order, it is not possible to define the trait attribute that references the other class, since the class it needs to refer to has not yet been defined. This is illustrated in the following example:

# circular_definition.py --- Non-working example of mutually-

#                            referring classes

from enthought.traits.api import HasTraits, Trait

 

class Chicken(HasTraits):

    hatched_from = Trait(Egg)

 

class Egg(HasTraits):

    created_by = Trait(Chicken)

As it stands, this example will not run because the hatched_from attribute references the Egg class, which has not yet been defined. Reversing the definition order of the classes does not fix the problem, because then the created_by trait references the Chicken class, which has not yet been defined.

The problem can be solved using the add_class_trait() method, as shown in the following code:

# add_class_trait.py --- Example of mutually-referring classes

#                        using add_class_trait()

from enthought.traits.api import HasTraits, Trait

 

class Chicken(HasTraits):

    pass

 

class Egg(HasTraits):

    created_by = Trait(Chicken)

 

Chicken.add_class_trait('hatched_from', Egg)

3.6                   Performance Considerations of Traits

Using traits can potentially impose a performance penalty on attribute access over and above that of normal Python attributes. For the most part, this penalty, if any, is small, because the core of the Traits package is written in C, just like the Python interpreter. In fact, for some common cases, subclasses of HasTraits can actually have the same or better performance than old or new style Python classes.

However, there are a couple of performance-related factors to keep in mind when defining classes and attributes using traits:

·    Whether a trait attribute delegates

·    The complexity of a trait definition

If a trait attribute does not delegate, the performance penalty can be characterized as follows:

·    Getting a value: No penalty (i.e., standard Python attribute access speed or faster)

·    Setting a value: Depends upon the complexity of the validation tests performed by the trait definition. Many of the predefined trait handlers defined in the Traits package support fast C-level validation. For most of these, the cost of validation is usually negligible. For other trait handlers, with Python-level validation methods, the cost can be quite a bit higher.

If a trait attribute does delegate, the cases to be considered are:

·    Getting the default value: Cost of following the delegation chain. The chain is resolved at the C level, and is quite fast, but its cost is linear with the number of delegation links that must be followed to find the default value for the trait.

·    Getting an explicitly assigned value: No penalty (i.e., standard Python attribute access speed or faster)

·    Setting: Cost of following the delegation chain plus the cost of per­forming the validation of the new value. The preceding discussions about delegation chain following and fast versus slow validation apply here as well.

Note that in the case where delegation modifies the delegate object, the cost of getting an attribute always includes the cost of following the delegation chain.

In a typical application scenario, where attributes are read more often than they are written, and delegation is not used, the impact of using traits is often minimal, because the only cost occurs when attributes are assigned and validated.

The worst case scenario occurs when delegation is used heavily to provide attributes with default values that are seldom changed. In this case, the cost of frequently following delegation chains may impose a measurable performance detriment on the application. Of course, this is offset by the convenience and flexibility provided by the delegation model. As with any powerful tool, it is best to understand its strengths and weaknesses and apply that understanding in determining when use of the tool is justified and appropriate.

 

Previous: 2 Defining Traits: Initialization and Validation Table of Contents  
Traits User Manual