File: custom.rst

package info (click to toggle)
python-traits 6.3.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 8,388 kB
  • sloc: python: 34,202; ansic: 4,173; makefile: 99
file content (599 lines) | stat: -rw-r--r-- 22,149 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
.. index:: custom traits

.. _custom-traits:

=============
Custom Traits
=============

The predefined traits such as those described in :ref:`predefined-traits` are
handy shortcuts for commonly used types. However, the Traits package also
provides facilities for defining complex or customized traits:

* Subclassing of traits
* The Trait() factory function
* Predefined or custom trait handlers

.. index:: subclassing traits, TraitType class

.. _trait-subclassing:

Trait Subclassing
-----------------

Starting with Traits version 3.0, most predefined traits are defined as
subclasses of traits.trait_handlers.TraitType. As a result, you can
subclass one of these traits, or TraitType, to derive new traits. Refer to the
*Traits API Reference* to see whether a particular predefined trait derives from
TraitType.

.. index::
   pair: subclassing traits; examples

Here's an example of subclassing a predefined trait class::

    # trait_subclass.py -- Example of subclassing a trait class
    from traits.api import BaseInt

    class OddInt ( BaseInt ):

        # Define the default value
        default_value = 1

        # Describe the trait type
        info_text = 'an odd integer'

        def validate ( self, object, name, value ):
            value = super().validate(object, name, value)
            if (value % 2) == 1:
                return value

            self.error( object, name, value )

The OddInt class defines a trait that must be an odd integer. It derives from
BaseInt, rather than Int, as you might initially expect. BaseInt and Int are
exactly the same, except that Int has a **fast_validate attribute**, which
causes it to quickly check types at the C level, not go through the expense of
executing the general validate() method. [6]_

As a subclass of BaseInt, OddInt can reuse and change any part of the BaseInt
class behavior that it needs to. In this case, it reuses the BaseInt class's
validate() method, via the call to super() in the OddInt validate() method.
Further, OddInt is related to BaseInt, which can be useful as documentation, and
in programming.

You can use the subclassing strategy to define either a trait type or a trait
property, depending on the specific methods and class constants that you define.
A trait type uses a validate() method, while a trait property uses get() and
set() methods.

The :class:`~.TraitType` initializer provides an optional argument
``default_value`` to support easy setting of the default value of the trait
type. The default value for that argument is :data:`~.NoDefaultSpecified`: we
don't follow the common Python idiom of using ``None`` to represent no default
here, since for many trait types ``None`` may be a valid default value. When
subclassing :class:`~.TraitType` and overriding or extending its ``__init__``
method, it's recommended to re-use the singleton :data:`~.NoDefaultSpecified`
if you need a way to indicate that no default value was specified.


.. index: trait type; defining

.. _defining-a-trait-type:

Defining a Trait Type
`````````````````````

The members that are specific to a trait type subclass are:

.. index:: default_value attribute, get_default_value()

* validate() method
* post_setattr() method
* **default_value** attribute or get_default_value() method

Of these, only the validate() method must be overridden in trait type
subclasses.

A trait type uses a validate() method to determine the validity of values
assigned to the trait. Optionally, it can define a post_setattr() method, which
performs additional processing after a value has been validated and assigned.

The signatures of these methods are:

.. method:: validate( object, name, value )
.. method:: post_setattr( object, name, value )

The parameters of these methods are:

.. index:: object parameter; validate(), name parameter; validate()
.. index:: value parameter; validate()

* *object*: The object whose trait attribute whose value is being assigned.
* *name*: The name of the trait attribute whose value is being assigned.
* *value*: The value being assigned.

The validate() method returns either the original value or any suitably coerced
or adapted value that is legal for the trait. If the value is not legal, and
cannot be coerced or adapted to be legal, the method must either raise a
TraitError, or calls the error() method to raise a TraitError on its behalf.

The subclass can define a default value either as a constant or as a computed
value. To use a constant, set the class-level **default_value attribute**. To
compute the default value, override the TraitType class's get_default_value()
method.

.. index:: trait property; defining

.. _defining-a-trait-property:

Defining a Trait Property
`````````````````````````

A trait property uses get() and set() methods to interact with the value of the
trait. If a TraitType subclass contains a get() method or a set() method, any
definition it might have for validate() is ignored.

The signatures of these methods are:

.. method:: get( object, name)
.. method:: set( object, name, value)

In these signatures, the parameters are:

* *object*: The object that the property applies to.
* *name*: The name of the trait property attribute on the object.
* *value*: The value being assigned to the property.

If only a get() method is defined, the property behaves as read-only. If only a
set() method is defined, the property behaves as write-only.

The get() method returns the value of the *name* property for the specified
object. The set() method does not return a value, but will raise a TraitError if
the specified *value* is not valid, and cannot be coerced or adapted to a valid
value.

In order for the property to trigger notifications you must call either:

* object.trait_property_changed(name, old, value) to not cache the value.
* self.set_value(object, name, value) to cache the value.

Likewise if the property will not be read only the get method must use
self.get_value(object, name) in order to behave correctly.

The following example demonstrates the use of a property trait to set temperature::

    class TempFloat(BaseFloat):
        default_value = 20.0
        info_text = 'A calculated temperature float in Celsius'

        def set(self, obj, name, value):
            celsius = (value - 32) * (5/9)
            setattr(self, name, celsius)
            self.set_value(obj, name, celsius)

        def get(self, obj, name):
            val = self.get_value(obj, name)
            if val is None:
                val = self.default_value
            return val

.. index:: TraitType class; members

.. _other-traittype-members:

Other TraitType Members
```````````````````````

The following members can be specified for either a trait type or a trait
property:

.. index:: info_text attribute, info(), init(), create_editor()

* **info_text** attribute or info() method
* init() method
* create_editor() method

A trait must have an information string that describes the values accepted by
the trait type (for example 'an odd integer'). Similarly to the default value,
the subclass's information string can be either a constant string or a computed
string. To use a constant, set the class-level info_text attribute. To compute
the info string, override the TraitType class's info() method, which takes no
parameters.

If there is type-specific initialization that must be performed when the trait
type is created, you can override the init() method. This method is
automatically called from the __init__() method of the TraitType class.

If you want to specify a default TraitsUI editor for the new trait type, you
can override the create_editor() method. This method has no parameters, and
returns the default trait editor to use for any instances of the type.

For complete details on the members that can be overridden, refer to the *Traits
API Reference* sections on the TraitType and BaseTraitHandler classes.

.. index:: Trait()

.. _the-trait-factory-function:

The Trait() Factory Function
----------------------------

The Trait() function is a generic factory for trait definitions. It has many
forms, many of which are redundant with the predefined shortcut traits. For
example, the simplest form Trait(default_value), is equivalent to the functions
for simple types described in :ref:`predefined-traits-for-simple-types`. For the
full variety of forms of the Trait() function, refer to the *Traits API
Reference*.

The most general form of the Trait() function is:

.. currentmodule:: traits.traits
.. function:: Trait(default_value, {type | constant_value | dictionary | class | function | trait_handler | trait }+ )
    :noindex:

.. index:: compound traits

The notation ``{ | | }+`` means a list of one or more of any of the items listed
between the braces. Thus, this form of the function consists of a default value,
followed by one or more of several possible items. A trait defined with multiple
items is called a compound trait. When more than one item is specified, a trait
value is considered valid if it meets the criteria of at least one of the items
in the list.

.. index::
   pair: Trait() function; examples

The following is an example of a compound trait with multiple criteria::

    # compound.py -- Example of multiple criteria in a trait definition
    from traits.api import HasTraits, Trait, Range

    class Die ( HasTraits ):

        # Define a compound trait definition:
        value = Trait( 1, Range( 1, 6 ),
                      'one', 'two', 'three', 'four', 'five', 'six' )

The Die class has a **value trait**, which has a default value of 1, and can have
any of the following values:

* An integer in the range of 1 to 6
* One of the following strings: 'one', 'two', 'three', 'four', 'five', 'six'

.. index:: Trait(); parameters

.. _trait-parameters:

Trait () Parameters
```````````````````

The items listed as possible arguments to the Trait() function merit some
further explanation.

.. index:: type; parameter to Trait(), constant_value parameter to Trait()
.. index:: dictionary parameter to Trait(), class parameter to Trait()
.. index:: function parameter to Trait(), trait handler; parameter to Trait()
.. index:: trait; parameter to Trait()

* *type*: See :ref:`type`.
* *constant_value*: See :ref:`constant-value`.
* *dictionary*: See :ref:`mapped-traits`.
* *class*: Specifies that the trait value must be an instance of the specified
  class or one of its subclasses.
* *function*: A "validator" function that determines whether a value being
  assigned to the attribute is a legal value. Traits version 3.0 provides a
  more flexible approach, which is to subclass an existing trait (or TraitType)
  and override the validate() method.
* *trait_handler*: See :ref:`trait-handlers`.
* *trait*: Another trait object can be passed as a parameter; any value that is
  valid for the specified trait is also valid for the trait referencing it.

.. index:: type; parameter to Trait()

.. _type:

Type
::::

A *type* parameter to the Trait() function can be any of the following standard
Python types:

* str or StringType
* int or IntType
* float or FloatType
* complex or ComplexType
* bool or BooleanType
* list or ListType
* tuple or TupleType
* dict or DictType
* FunctionType
* MethodType
* type
* NoneType

Specifying one of these types means that the trait value must be of the
corresponding Python type.

.. index:: constant_value parameter to Trait()

.. _constant-value:

Constant Value
::::::::::::::

A *constant_value* parameter to the Trait() function can be any constant
belonging to one of the following standard Python types:

* NoneType
* int
* float
* complex
* bool
* str

Specifying a constant means that the trait can have the constant as a valid
value. Passing a list of constants to the Trait() function is equivalent to
using the Enum predefined trait.

.. index:: mapped traits

.. _mapped-traits:

Mapped Traits
`````````````

If the Trait() function is called with parameters that include one or more
dictionaries, then the resulting trait is called a "mapped" trait. In practice,
this means that the resulting object actually contains two attributes:

.. index:: shadow values

* An attribute whose value is a key in the dictionary used to define the trait.
* An attribute containing its corresponding value (i.e., the mapped or
  "shadow" value). The name of the shadow attribute is simply the base
  attribute name with an underscore appended.

Mapped traits can be used to allow a variety of user-friendly input values to be
mapped to a set of internal, program-friendly values.

.. index:: mapped traits; examples

The following examples illustrates mapped traits that map color names to tuples
representing red, green, blue, and transparency values::

    # mapped.py --- Example of a mapped trait
    from traits.api import HasTraits, Trait

    standard_color = Trait ('black',
                  {'black':       (0.0, 0.0, 0.0, 1.0),
                   'blue':        (0.0, 0.0, 1.0, 1.0),
                   'cyan':        (0.0, 1.0, 1.0, 1.0),
                   'green':       (0.0, 1.0, 0.0, 1.0),
                   'magenta':     (1.0, 0.0, 1.0, 1.0),
                   'orange':      (0.8, 0.196, 0.196, 1.0),
                   'purple':      (0.69, 0.0, 1.0, 1.0),
                   'red':         (1.0, 0.0, 0.0, 1.0),
                   'violet':      (0.31, 0.184, 0.31, 1.0),
                   'yellow':      (1.0, 1.0, 0.0, 1.0),
                   'white':       (1.0, 1.0, 1.0, 1.0),
                   'transparent': (1.0, 1.0, 1.0, 0.0) } )

    red_color = Trait ('red', standard_color)

    class GraphicShape (HasTraits):
        line_color = standard_color
        fill_color = red_color

The GraphicShape class has two attributes: **line_color** and **fill_color**.
These attributes are defined in terms of the **standard_color** trait, which
uses a dictionary. The **standard_color** trait is a mapped trait, which means
that each GraphicShape instance has two shadow attributes: **line_color_**
and **fill_color_**. Any time a new value is assigned to either **line_color**
or **fill_color**, the corresponding shadow attribute is updated with the
value in the dictionary corresponding to the value assigned. For example::

    >>> import mapped
    >>> my_shape1 = mapped.GraphicShape()
    >>> print(my_shape1.line_color, my_shape1.fill_color)
    black red
    >>> print(my_shape1.line_color_, my_shape1.fill_color_)
    (0.0, 0.0, 0.0, 1.0) (1.0, 0.0, 0.0, 1.0)
    >>> my_shape2 = mapped.GraphicShape()
    >>> my_shape2.line_color = 'blue'
    >>> my_shape2.fill_color = 'green'
    >>> print(my_shape2.line_color, my_shape2.fill_color)
    blue green
    >>> print(my_shape2.line_color_, my_shape2.fill_color_)
    (0.0, 0.0, 1.0, 1.0) (0.0, 1.0, 0.0, 1.0)

This example shows how a mapped trait can be used to create a user-friendly
attribute (such as **line_color**) and a corresponding program-friendly shadow
attribute (such as **line_color_**). The shadow attribute is program-friendly
because it is usually in a form that can be directly used by program logic.

There are a few other points to keep in mind when creating a mapped trait:

* If not all values passed to the Trait() function are dictionaries, the
  non-dictionary values are copied directly to the shadow attribute (i.e.,
  the mapping used is the identity mapping).
* Assigning directly to a shadow attribute (the attribute with the trailing
  underscore in the name) is not allowed, and raises a TraitError.

The concept of a mapped trait extends beyond traits defined via a dictionary.
Any trait that has a shadow value is a mapped trait. For example, for the
Expression trait, the assigned value must be a valid Python expression, and the
shadow value is the compiled form of the expression.

.. index:: trait handler; classes

.. _trait-handlers:

Trait Handlers
--------------

In some cases, you may want to define a customized trait that is unrelated to
any predefined trait behavior, or that is related to a predefined trait that
happens to not be derived from TraitType. The option for such cases is to use a
trait handler, either a predefined one or a custom one that you write.

.. index:: TraitHandler class

A trait handler is an instance of the
traits.trait_handlers.TraitHandler class, or of a subclass, whose
task is to verify the correctness of values assigned to object traits. When a
value is assigned to an object trait that has a trait handler, the trait
handler's validate() method checks the value, and assigns that value or a
computed value, or raises a TraitError if the assigned value is not valid. Both
TraitHandler and TraitType derive from BaseTraitHandler; TraitHandler has a more
limited interface.

The Traits package provides a number of predefined TraitHandler subclasses. A few
of the predefined trait handler classes are described in the following sections.
These sections also demonstrate how to define a trait using a trait handler and
the Trait() factory function. For a complete list and descriptions of predefined
TraitHandler subclasses, refer to the *Traits API Reference*, in the section on
the traits.trait_handlers module.

.. index:: TraitPrefixList class

.. _traitprefixlist:

TraitPrefixList
```````````````

The TraitPrefixList handler accepts not only a specified set of strings as
values, but also any unique prefix substring of those values. The value assigned
to the trait attribute is the full string that the substring matches.

.. index::
   pair: TraitPrefixList class; examples

For example::

    >>> from traits.api import HasTraits, Trait
    >>> from traits.api import TraitPrefixList
    >>> class Alien(HasTraits):
    ...   heads = Trait('one', TraitPrefixList(['one','two','three']))
    ...
    >>> alf = Alien()
    >>> alf.heads = 'o'
    >>> print(alf.heads)
    one
    >>> alf.heads = 'tw'
    >>> print(alf.heads)
    two
    >>> alf.heads = 't'  # Error, not a unique prefix
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "c:\svn\ets3\traits_3.0.3\enthought\traits\trait_handlers.py", line 1802,
     in validate self.error( object, name, value )
      File "c:\svn\ets3\traits_3.0.3\enthought\traits\trait_handlers.py", line 175,
    in error value )
    traits.trait_errors.TraitError: The 'heads' trait of an Alien instance
     must be 'one' or 'two' or 'three' (or any unique prefix), but a value of 't'
     <type 'str'> was specified.

.. index:: TraitPrefixMap class

.. _traitprefixmap:

TraitPrefixMap
``````````````

The TraitPrefixMap handler combines the TraitPrefixList with mapped traits. Its
constructor takes a parameter that is a dictionary whose keys are strings. A
string is a valid value if it is a unique prefix for a key in the dictionary.
The value assigned is the dictionary value corresponding to the matched key.

.. index::
   pair: TraitPrefixMap class; examples

The following example uses TraitPrefixMap to define a Boolean trait that accepts
any prefix of 'true', 'yes', 'false', or 'no', and maps them to 1 or 0.
::

    # traitprefixmap.py --- Example of using the TraitPrefixMap handler
    from traits.api import Trait, TraitPrefixMap

    boolean_map = Trait('true', TraitPrefixMap( {
                                  'true': 1,
                                  'yes':  1,
                                  'false': 0,
                                  'no':   0 } ) )

.. index:: handler classes; custom

.. _custom-trait-handlers:

Custom Trait Handlers
---------------------

If you need a trait that cannot be defined using a predefined trait handler
class, 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.

.. index::
   pair: custom trait handler; examples

.. _example-custom-trait-handler:

Example Custom Trait Handler
````````````````````````````

The following example defines the OddInt trait (also implemented as a trait type
in :ref:`defining-a-trait-type`) using a TraitHandler subclass.
::

    # custom_traithandler.py --- Example of a custom TraitHandler
    import types
    from 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 traits.api import HasTraits, Range, Trait
    from custom_traithandler import TraitOddInteger

    class AnOddClass(HasTraits):
        oddball = Trait(1, TraitOddInteger())
        very_odd = Trait(-1, TraitOddInteger(), Range(-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:\wrk\src\lib\enthought\traits\traits.py", line 1119, in validate
        raise TraitError(excp)
    traits.traits.TraitError: The 'very_odd' trait of an AnOddClass instance
    must be **a positive odd integer** or -10 <= an integer <= -1, but a value
    of 0 <type 'int'> was specified.

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

.. rubric:: Footnotes

.. [6] All of the basic predefined traits (such as Float and Str) have a
   BaseType version that does not have the **fast_validate** attribute.