File: deferring.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 (281 lines) | stat: -rw-r--r-- 11,089 bytes parent folder | download | duplicates (3)
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
.. index:: deferral

.. _deferring-traits:

Deferring Trait Definitions
===========================

One of the advanced capabilities of the Traits package is its support for trait
attributes to defer their definition and value 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. Deferring leverages the common "has-a" relationship between
objects, rather than the "is-a" relationship that class inheritance provides.

.. index:: delegation, prototyping

There are two ways that a trait attribute can defer to another object's
attribute: *delegation* and *prototyping*. In delegation, the deferring
attribute is a complete reflection of the delegate attribute. Both the value and
validation of the delegate attribute are used for the deferring attribute;
changes to either one are reflected in both. In prototyping, the deferring
attribute gets its value and validation from the prototype attribute, *until the
deferring attribute is explicitly changed*. At that point, while the deferring
attribute still uses the prototype's validation, the link between the values is
broken, and the two attributes can change independently. This is essentially a
"copy on write" scheme.

The concepts of delegation and prototyping are implemented in the Traits package
by two classes derived from TraitType: DelegatesTo and PrototypedFrom. [5]_

.. _delegatesto:

DelegatesTo
-----------

.. class:: DelegatesTo(delegate[, prefix='', listenable=True, **metadata])

.. index:: delegate parameter to DelegatesTo initializer

The *delegate* parameter is a string that specifies the name of an attribute
on the same object, which refers to the object whose attribute is deferred to;
it is usually an Instance trait. The value of the delegating attribute changes
whenever:

* The value of the appropriate attribute on the delegate object changes.
* The object referenced by the trait named in the *delegate* parameter changes.
* The delegating attribute is explicitly changed.

Changes to the delegating attribute are propagated to the delegate object's
attribute.

The *prefix* and *listenable* parameters to the initializer function specify
additional information about how to do the delegation.

.. index::
   pair: delegation; examples

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
DelegatesTo object. Consider the following example::

    # delegate.py --- Example of trait delegation
    from traits.api \
        import DelegatesTo, HasTraits, Instance, Str

    class Parent(HasTraits):
        first_name = Str
        last_name  = Str

    class Child(HasTraits):
        first_name = Str
        last_name  = DelegatesTo('father')
        father     = Instance(Parent)
        mother     = Instance(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 = 'Cooper' # Updates delegatee
    >>> print(tony.last_name)
    Cooper
    >>> sally.last_name = sally.mother # ERR: string expected
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
      File "c:\src\trunk\enthought\traits\trait_handlers.py", line
    163, in error
        raise TraitError( object, name, self.info(), value )
    traits.trait_errors.TraitError:  The 'last_name' trait of a
    Parent instance must be a string, but a value of <__main__.Parent object at
    0x014D6D80> <class '__main__.Parent'> was specified.
    """

A Child object delegates its **last_name** attribute value to its **father**
object's **last_name** attribute. Because the *prefix* parameter was not
specified in the DelegatesTo initializer, the attribute name on the delegatee is
the same as the original attribute name. Thus, the **last_name** of a Child is
the same as the **last_name** of its **father**. When either the **last_name**
of the Child or the **last_name** of the father is changed, both attributes
reflect the new value.

.. _prototypedfrom:

PrototypedFrom
--------------
.. class:: PrototypedFrom(prototype[, prefix='', listenable=True, **metadata])

.. index:: prototype parameter to PrototypesFrom

The *prototype* parameter is a string that specifies the name of an attribute on
the same object, which refers to the object whose attribute is prototyped; it is
usually an Instance trait. The prototyped attribute behaves similarly to a
delegated attribute, until it is explicitly changed; from that point forward,
the prototyped attribute changes independently from its prototype.

The *prefix* and *listenable* parameters to the initializer function specify
additional information about how to do the prototyping.

.. _keyword-parameters:

Keyword Parameters
------------------

The *prefix* and *listenable* parameters of the DelegatesTo and PrototypedFrom
initializer functions behave similarly for both classes.

.. index:: prefix parameter to initializer methods

.. _prefix-keyword:

Prefix Keyword
``````````````

When the *prefix* parameter is a non-empty string, the rule for performing trait
attribute look-up in the deferred-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 looking up the deferred-to attribute.
* 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 looking up the object attribute.
* 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 looking up the object attribute.

.. index::
   single: examples; prototype prefix
   pair: examples; prototyping

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

    # prototype_prefix.py --- Examples of PrototypedFrom()
    #                         prefix parameter
    from traits.api import \
        PrototypedFrom, Float, HasTraits, Instance, Str

    class Parent (HasTraits):
        first_name = Str
        family_name = ''
        favorite_first_name = Str
        child_allowance = Float(1.00)
    class Child (HasTraits):
        __prefix__ = 'child_'
        first_name = PrototypedFrom('mother', 'favorite_*')
        last_name  = PrototypedFrom('father', 'family_name')
        allowance  = PrototypedFrom('father', '*')
        father     = Instance(Parent)
        mother     = Instance(Parent)

    """
    >>> fred = Parent( first_name = 'Fred', family_name = 'Lopez', \
    ... favorite_first_name = 'Diego', child_allowance = 5.0 )
    >>> maria = Parent(first_name = 'Maria', family_name = 'Gonzalez',\
    ... favorite_first_name = 'Tomas', child_allowance = 10.0 )
    >>> nino = Child( father=fred, mother=maria )
    >>> print('%s %s gets $%.2f for allowance' % (nino.first_name, \ ... nino.last_name, nino.allowance))
    Tomas Lopez gets $5.00 for allowance
    """

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

* **first_name**, which prototypes from the **favorite_first_name** attribute
  of its **mother** object.
* **last_name**, which prototyped from the **family_name attribute** of its
  **father** object.
* **allowance**, which prototypes from the **child_allowance** attribute of its
  **father** object.

.. index:: listenable parameter to initializer methods

.. _listenable-keyword:

Listenable Keyword
``````````````````

By default, you can attach listeners to deferred trait attributes, just as you
can attach listeners to most other trait attributes, as described in the
following section. However, implementing the notifications correctly requires
hooking up complicated listeners under the covers. Hooking up these listeners
can be rather more expensive than hooking up other listeners. Since a common use
case of deferring is to have a large number of deferred attributes for static
object hierarchies, this feature can be turned off by setting
``listenable=False`` in order to speed up instantiation.

.. index::
   single: deferral; notification with
   pair: examples; deferral

.. _notification-with-deferring:

Notification with Deferring
---------------------------

While two trait attributes are linked by a deferring relationship (either
delegation, or prototyping before the link is broken), notifications for changes
to those attributes are linked as well. When the value of a deferred-to
attribute changes, notification is sent to any handlers on the deferring object,
as well as on the deferred-to object. This behavior is new in Traits version
3.0. In previous versions, only handlers for the deferred-to object (the object
directly changed) were notified. This behavior is shown in the following
example::

    # deferring_notification.py -- Example of notification with deferring
    from traits.api \
        import HasTraits, Instance, PrototypedFrom, Str

    class Parent ( HasTraits ):

        first_name = Str
        last_name  = Str

        def _last_name_changed(self, new):
            print("Parent's last name changed to %s." % new)

    class Child ( HasTraits ):

        father = Instance( Parent )
        first_name = Str
        last_name  = PrototypedFrom( 'father' )

        def _last_name_changed(self, new):
            print("Child's last name changed to %s." % new)

    """
    >>> dad = Parent( first_name='William', last_name='Chase' )
    Parent's last name changed to Chase.
    >>> son = Child( first_name='John', father=dad )
    Child's last name changed to Chase.
    >>> dad.last_name='Jones'
    Parent's last name changed to Jones.
    Child's last name changed to Jones.
    >>> son.last_name='Thomas'
    Child's last name changed to Thomas.
    >>> dad.last_name='Riley'
    Parent's last name changed to Riley.
    >>> del son.last_name
    Child's last name changed to Riley.
    >>> dad.last_name='Simmons'
    Parent's last name changed to Simmons.
    Child's last name changed to Simmons.
    """

Initially, changing the last name of the father triggers notification on both
the father and the son. Explicitly setting the son's last name breaks the
deferring link to the father; therefore changing the father's last name does not
notify the son. When the son reverts to using the father's last name (by
deleting the explicit value), changes to the father's last name again affect and
notif

.. rubric:: Footnotes

.. [5] Both of these class es inherit from the Delegate class. Explicit use of
   Delegate is deprecated, as its name and default behavior (prototyping) are
   incongruous.