File: 2_initialization.py

package info (click to toggle)
python-traits 6.4.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,648 kB
  • sloc: python: 34,801; ansic: 4,266; makefile: 102
file content (192 lines) | stat: -rw-r--r-- 6,101 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
# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

"""
Initialization
==============

If you have done any significant amount of object-oriented Python
programming, you may have noticed that your ``__init__`` methods often
have a lot of boilerplate.  In our original example, the code copies all of
the ``__init__`` arguments to corresponding attributes before doing any
real work::

    def __init__(self, filename, sample_id, date_acquired, operator,
                 scan_size=(1e-5, 1e-5)):
        # initialize the primary attributes
        self.filename = filename
        self.sample_id = sample_id
        self.operator = operator
        self.scan_size = scan_size

Traits lets you avoid this boilerplate by defining a default ``__init__``
method that accepts keyword arguments that correspond to the declared
traits.  The Traits version of the ``Image`` class could potentially skip
the ``__init__`` method entirely::

    class Image(HasTraits):
        filename = File(exists=True)
        sample_id = Str()
        date_acquired = Date()
        operator = Str()
        scan_size = Tuple(Float, Float)


    # this works!
    image = Image(
        filename=filename,
        operator="Hannes",
        sample_id="0001",
        date_acquired=datetime.datetime.today(),
        scan_size=(1e-5, 1e-5),
    )

Default Values
--------------

There are a couple of complications in the example that we need to take into
account.  The first is what happens if a user forgets to provide an initial
value::

    >>> image = Image(
    ...     filename=filename,
    ...     sample_id="0001",
    ...     date_acquired=datetime.datetime.today(),
    ...     scan_size=(1e-5, 1e-5),
    ... )
    >>> image.operator
    ""

As this example shows, the operator gets given a default value of the empty
string ``""``.  In fact every trait type comes with an default value.  For
numeric trait types, like ``Int`` and ``Float``, the default is 0.  For
``Str`` trait types it is the empty string, for ``Bool`` traits it is
``False``, and so on.

However, that might not be what you want as your default value.  For example,
you might want to instead flag that the operator has not been provided with
the string ``"N/A"`` for "not available".  Most trait types allow you to
specify a default value as part of the declaration.  So we could say::

    operator = Str("N/A")

and now if we omit ``operator`` from the arguments, we get::

    >>> image.operator
    "N/A"


Dynamic Defaults
----------------

The second complication comes from more complex initial values.  For example,
we could declare some arbitrary fixed date as the default value for
``date_acquired``::

    date_acquired = Date(datetime.date(2020, 1, 1))

But it would be better if we could set it to a dynamic value.  For example,
a reasonable default would be today's date.  You can provide this sort of
dynamically declared default by using a specially-named method which has
the pattern ``_<trait-name>_default`` and which returns the default value.
So we could write::

    def _date_acquired_default(self):
        return datetime.datetime.today()

Dynamic defaults are best used for values which don't depend on other traits.
For example, it might be tempting to have the ``image`` trait have a dynamic
default which loads in the data.  As we will see, this is almost always
better handled by Traits observation and/or properties, which are discussed
in subsequent sections of the tutorial.


The ``traits_init`` Method
--------------------------

Although you aren't required to write an ``__init__`` method in a
``HasTraits`` subclass, you can always choose to do so.  If you do, you
**must** call ``super()`` to ensure that Traits has a chance to set up
its machinery.  In our example the ``__init__`` method is also used to set
up some auxiliary values. This doesn't have to change::

    def __init__(self, **traits):
        super().__init__(**traits)

        # useful secondary attributes
        self.scan_width, self.scan_height = self.scan_size

However Traits offers a slightlty more convenient way of doing this sort of
post-initialization setup of state: you can define a ``traits_init`` method
which the ``HasTraits`` class ensures is called as part of the main
initialization process.  When it has been called, all initial values will
have been set::

    def traits_init(self):
        # useful secondary attributes
        self.scan_width, self.scan_height = self.scan_size


Exercise
--------

In our original example, the ``scan_size`` atribute had a default value of
``(1e-5, 1e-5)``.  Modify the code in the example so that the trait is
initialized to this default using a dynamic default method.

"""


import datetime
import os

from traits.api import Array, Date, HasTraits, File, Float, Str, Tuple


class Image(HasTraits):
    """ An SEM image stored in a file. """

    filename = File(exists=True)

    sample_id = Str()

    date_acquired = Date()

    operator = Str("N/A")

    scan_size = Tuple(Float, Float)

    image = Array(shape=(None, None), dtype='uint8')

    def traits_init(self):
        # useful secondary attributes
        self.scan_width, self.scan_height = self.scan_size

    def _date_acquired_default(self):
        return datetime.datetime.today()


# ---------------------------------------------------------------------------
# Demo code
# ---------------------------------------------------------------------------

this_dir = os.path.dirname(__file__)
image_dir = os.path.join(this_dir, "images")
filename = os.path.join(image_dir, "sample_0001.png")

# load the image
image = Image(
    filename=filename,
    sample_id="0001",
    scan_size=(1e-5, 1e-5),
)

# show all the defined traits
image.print_traits()