# Copyright (c) 2007, Enthought, Inc. # License: BSD Style. #--(on_trait_change Decorator)-------------------------------------------------- """ on_trait_change Decorator ========================= Until Traits 3.0, the only way to define a static trait notification handler as part of a class definition was to define the method following a certain naming convention:: def _name_changed ( self, ... ): ... or def _name_fired ( self, ... ): ... where *name* is the name of the trait to which the notification handler method applies. Starting with Traits 3.0, there is now an alternate method for declaring notification handlers using the **on_trait_change** decorator. The syntax for the decorator is:: @on_trait_change( 'extended_trait_name' ) def any_method_name ( self, ... ): ... where *extended_trait_name* is the name of the trait for which the following method is the notification handler, and *any_method_name* is an arbitrary method name, which does not need to follow any particular naming convention. Although the new syntax is more verbose than the original syntax, it has several advantages: - It does not require using special method names. - It allows using the extended trait naming support added to the **on_trait_change** method in Traits 3.0. The last item is especially important since it allows you to statically declare notification handlers for many more cases than were previously possible. For example:: @on_trait_change( 'foo,bar,baz' ) def _update ( self ): ...perform update logic... defines an *_update* method that is called whenever the class's *foo*, *bar* or *baz* traits change. Previously, this would have required writing three separate notification handlers (one each for the *foo*, *bar* and *baz* traits), or adding *event* metadata to each of the trait declarations for *foo*, *bar* and *baz*. Similarly, the previous technique of writing methods such as:: def _bar_changed_for_foo ( self, ... ): ... which statically defines a notification handler for changes to the *bar* trait of the object's *foo* trait can now be written as:: @on_trait_change( 'foo.bar' ) def any_method_name ( self, ... ): ... Perhaps even more importantly, this technique can be applied in even more complex situations, such as:: @on_trait_change( 'foo.bar.baz' ) def any_method_name ( self, ... ): ... or @on_trait_change( 'foo.+dirty,foo2.[bar,baz,foogle]' ) def any_method_name ( self, ... ): ... The first case is a simple extension of the previous example, while the second is a somewhat far-fetched example which can be interpreted as defining a method that handles: - Changes to any trait on the object's *foo* trait which has *dirty* metadata defined, or - Changes to the *bar*, *baz* or *foogle* trait of the object's *foo2* trait. Note that there is one important semantic difference between writing:: def _name_changed ( self, ... ): ... and:: @on_trait_change( 'name' ) def any_method_name ( self, ... ): ... While both are recognized as being notification handlers for the object's *name* trait, the interpretation of the argument signature for the first case follows the static trait change handler pattern, while the second case follows the dynamic **on_trait_change** method pattern. While this might seem obvious, given the fact that the decorator is called *on_trait_change*, it is an important enough difference to note explicitly. A Complete Example ------------------ Refer to the code tabs of this lesson for a complete example using the *on_trait_change* decorator. In particular, look at the definition of the *sick_again* method in the **Corporation Class** tab. """ #--<Imports>-------------------------------------------------------------------- from traits.api import * #--[Employee Class]------------------------------------------------------------- class Employee ( HasTraits ): # The name of the employee: name = Str # The number of sick days they have taken this year: sick_days = Int #--[Department Class]----------------------------------------------------------- class Department ( HasTraits ): # The name of the department: name = Str # The employees in the department: employees = List( Employee ) #--[Corporation Class]---------------------------------------------------------- class Corporation ( HasTraits ): # The name of the corporation: name = Str # The departments within the corporation: departments = List( Department ) # Define a corporate 'whistle blower' method: @on_trait_change( 'departments:employees.sick_days' ) def sick_again ( self, object, name, old, new ): print '%s just took sick day number %d for this year!' % ( object.name, new ) #--[Example*]------------------------------------------------------------------- # Create some sample employees: millie = Employee( name = 'Millie', sick_days = 2 ) ralph = Employee( name = 'Ralph', sick_days = 3 ) tom = Employee( name = 'Tom', sick_days = 1 ) slick = Employee( name = 'Slick', sick_days = 16 ) marcelle = Employee( name = 'Marcelle', sick_days = 7 ) reggie = Employee( name = 'Reggie', sick_days = 11 ) dave = Employee( name = 'Dave', sick_days = 0 ) bob = Employee( name = 'Bob', sick_days = 1 ) alphonse = Employee( name = 'Alphonse', sick_days = 5 ) # Create some sample departments: accounting = Department( name = 'accounting', employees = [ millie, ralph, tom ] ) sales = Department( name = 'Sales', employees = [ slick, marcelle, reggie ] ) development = Department( name = 'Development', employees = [ dave, bob, alphonse ] ) # Create a sample corporation: acme = Corporation( name = 'Acme, Inc.', departments = [ accounting, sales, development ] ) # Now let's try out our 'reporting' system: slick.sick_days += 1 reggie.sick_days += 1