|
114 | 114 | %
|
115 | 115 | Our class \pythonil{Point} will have two attributes, \pythonil{x} and~\pythonil{y}.
|
116 | 116 | A attribute is a variable that every single instance of the class has.
|
117 |
| -In other words, we later want to be able to create one instance of \pythonil{Point} with the x\nobreakdashes-coordinate~5 and the y\nobreakdashes-coordinate~10 and then another instance with the x\nobreakdashes-coordinate~2 and the y\nobreakdashes-coordinate~7. |
| 117 | +We later want to be able to create one instance of \pythonil{Point} with the x\nobreakdashes-coordinate~5 and the y\nobreakdashes-coordinate~10 and then another instance with the x\nobreakdashes-coordinate~2 and the y\nobreakdashes-coordinate~7. |
| 118 | +So each instance of \pythonil{Point} needs to have these two attributes. |
118 | 119 |
|
119 |
| -Therefore, \pythonil{Point} needs a initializer, i.e., a special method that creates these attributes. |
120 |
| -This method is called~\pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_}. |
| 120 | +Therefore, \pythonil{Point} needs a initializer, i.e., a special method that creates and initializes these attributes. |
| 121 | +This method is called~\dunder{init}. |
121 | 122 | As said, every method of a class must have a parameter~\pythonilIdx{self}, which is the instance of the class (the object) upon which the method is called.
|
122 |
| -The initializer \pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_} is a special method, so it also has this parameter~\pythonilIdx{self}. |
| 123 | +The initializer \dunder{init} is a special method, so it also has this parameter~\pythonilIdx{self}. |
123 | 124 | Additionally, we demand that two parameters~\pythonil{x} and~\pythonil{y} be passed in when we create an instance of~\pythonil{Point}.
|
124 |
| -We allow the values to be either \pythonils{int} or \pythonils{float}.% |
| 125 | +We allow the values to be either \pythonils{int} or \pythonils{float}. |
| 126 | + |
| 127 | +Inside every method of a class, the attributes of objects are accessed via the parameter~\pythonilIdx{self}. |
| 128 | +We can read the attribute~\pythonil{x} of an object inside a method of the class by writing~\pythonil{self.x}. |
| 129 | +Here, \pythonil{self.x} can be used just like a normal local variable. |
| 130 | +We can store the value~\pythonil{a} in a (mutable) attribute~\pythonil{x} of the current object in a method of the class by writing~\pythonil{self.x = a}. |
| 131 | +This value will then remain the same until it is changed, even after the execution of the method is completed.% |
125 | 132 | %
|
126 | 133 | \bestPractice{attributes}{%
|
127 |
| -Object attributes must only be created inside the initializer~\pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_}. % |
| 134 | +Object attributes must only be created inside the initializer~\dunder{init}. % |
128 | 135 | An initial value must immediately be assigned to each attribute.%
|
129 | 136 | }
|
130 | 137 | %
|
|
143 | 150 | In other words, we do not allow the coordinates of our \pythonils{Point} to change after object creation.%
|
144 | 151 | %
|
145 | 152 | \bestPractice{attributeTypeHint}{%
|
146 |
| -Every attribute of an object must be annotated with a \pgls{typeHint} and a documentation comment when created in the initializer~\pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_}~\cite{LM2024WTMD}. % |
| 153 | +Every attribute of an object must be annotated with a \pgls{typeHint} and a documentation comment when created in the initializer~\dunder{init}~\cite{LM2024WTMD}. % |
147 | 154 | \pglspl{typeHint} work as with normal variables.}%
|
148 | 155 | %
|
| 156 | +\bestPractice{attributeFinal}{% |
| 157 | +The \pgls{typeHint} \pythonilIdx{Final}\pythonIdx{typing!Final} marks an attribute as immutable. % |
| 158 | +All attributes that you do not intend to change should be annotated with~\pythonilIdx{Final}\pythonIdx{typing!Final}.% |
| 159 | +}% |
| 160 | +% |
149 | 161 | \bestPractice{attributeDocstring}{%
|
150 | 162 | An attribute is documented in the line \emph{above} the attribute initialization by writing a \emph{comment} starting with \pythonilIdx{\#: }, which explains the meaning of the attribute~\cite{SD2024DCAD}. %
|
151 | 163 | (Sometimes, the documentation is given as string directly below the attribute definition~\cite{PEP287}, but we stick to the former method, because it has proper tool support, e.g., by~\sphinx.)%
|
152 | 164 | }%
|
153 | 165 | %
|
154 |
| -After properly defining our initializer, we can now do something like \pythonil{p = Point(1, 2)} and it will create the object~\pythonil{p} which is an instance of the \pythonilIdx{class} \pythonil{Point}. |
155 |
| -This will allocate the memory for~\pythonil{p} and automatically invoke \pythonil{\_\_init\_\_(p, 1, 2)}. |
156 |
| -As a result, \pythonil{p.x} will have the value~\pythonil{1} and \pythonil{p.y} will have value~\pythonil{2}. |
157 |
| -Notice that we can immediately see from the knowledge that \pythonil{p} is an instance of \pythonil{Point} that \pythonil{p.x} and \pythonil{p.y} are its x\nobreakdashes-\ and y\nobreakdashes-coordinate, respectively. |
| 166 | +After properly defining our initializer, we can now do something like~\pythonil{p = Point(1, 2)}. |
| 167 | +This creates a new object as an instance of our \pythonilIdx{class} \pythonil{Point}. |
| 168 | +Therefore, first, the necessary memory is allocated. |
| 169 | +Then, the initializer is invoked as~\pythonil{\_\_init\_\_(p, 1, 2)}. |
| 170 | +As a result, \pythonil{p} now refers to a \pythonil{Point}~object. |
| 171 | +The attribute \pythonil{p.x} has the value~\pythonil{1} and \pythonil{p.y} has value~\pythonil{2}. |
| 172 | + |
| 173 | +From the knowledge that \pythonil{p} is an instance of \pythonil{Point}, we can immediately see that \pythonil{p.x} and \pythonil{p.y} are its x-\ and y\nobreakdashes-coordinate, respectively. |
158 | 174 | There is no way to mistake the meaning of these variables.
|
159 | 175 | Of course, our \pglspl{docstring} with \pglspl{doctest} and \pglspl{typeHint} further help the reader to understand their meaning.
|
160 | 176 |
|
161 |
| -Having a new container class for points in the two-dimensional plane is already nice. |
| 177 | +Having a new class for points in the two-dimensional plane is already nice. |
162 | 178 | But \pythoniles{class} also allow us to define operations on such points in form of methods.
|
163 | 179 | As an example, we implement a method \pythonil{distance} that computes the distance between two points.
|
164 | 180 | You would have a point~\pythonil{p1} and invoke \pythonil{p1.distance(p2)} to compute the distance to another point~\pythonil{p2}.
|
165 | 181 | The computation itself will follow \cref{eq:euclideanDistance} from our recent endeavor to operations on iterations in \cref{sec:operationsOnIterators}.
|
166 | 182 | We therefore need to import the \pythonilIdx{sqrt} function from the \pythonilIdx{math} module.
|
167 |
| -Our new method \pythonil{distance} will have two parameters, \pythonil{self}, which will be the object upon which we invoke the method~(\pythonil{p1}~in the above example) and~\pythonil{p}, the other object~(or \pythonil{p2} above). |
| 183 | + |
| 184 | +Our new method \pythonil{distance} will have two parameters, \pythonilIdx{self}, which will be the object upon which we invoke the method~(\pythonil{p1}~in the above example) and~\pythonil{p}, the other object~(or \pythonil{p2} above). |
168 | 185 | It then just has to compute the Euclidean distance~\pythonil{sqrt((self.x - p.x) ** 2 + (self.y - p.y) ** 2)}.
|
169 |
| -Notice that the \pgls{docstring} not just explains how this method is used, but also provides a simple example in form of a~\pgls{doctest}: |
| 186 | +Inside a method of an object, \pythonilIdx{self} always refers to the object itself. |
| 187 | +Therefore, \pythonil{self.x}~is the x\nobreakdashes-coordinate of the current object and \pythonil{self.y}~is its~y\nobreakdashes-coordinate. |
| 188 | +\pythonil{p.x}~is the x\nobreakdashes-coordinate of the point~\pythonil{p} that was passed in as actual parameter of the method, and \pythonil{p.y}~is its y\nobreakdashes-coordinate. |
| 189 | +Notice that the \pgls{docstring} not just explains how this method is used, but also provides a simple example in form of a~\pgls{doctest}. |
170 | 190 | If you compute \pythonil{Point(1, 1).distance(Point(4, 4))}, then the expected result is something like~4.243.%
|
171 | 191 | %
|
| 192 | +\begin{sloppypar}% |
| 193 | +In this \pgls{doctest} -- \pythonil{Point(1, 1).distance(Point(4, 4))} -- we only provided a single parameter to the method~\pythonil{distance}. |
| 194 | +When calling the method~\pythonil{distance}, we never need to provide a value of the parameter~\pythonilIdx{self} directly. |
| 195 | +Instead, it will be provided indirectly: |
| 196 | +If we have two points~\pythonil{p1} and~\pythonil{p2} and invoke~\pythonil{p1.distance(p2)}, then~\pythonil{self = p1} will be set automatically. |
| 197 | +Hence, even though we declared our method as~\pythonil{def distance(self, p: "Point") -> float}, which looks as if we need to provide two parameters~(\pythonilIdx{self} and~\pythonil{p}), we only need to provide one, namely~\pythonil{p}.% |
| 198 | +\end{sloppypar}% |
| 199 | +% |
| 200 | +Reading this again, we notice that the parameter~\pythonil{p} is annotated with a very strange \pgls{typeHint}: |
| 201 | +One would expect that we would annotate it with~\pythonil{Point}, instead it is annotated with the string~\pythonil{"Point"}. |
| 202 | +This has the simple reason that the complete class \pythonil{Point} is only defined \emph{after}, well, the complete definition of class~\pythonil{Point} and, therefore, not yet available as type \emph{inside} its definition. |
| 203 | +Using the string here is therefore just a crutch with no real other effect. |
| 204 | +% |
| 205 | +% |
172 | 206 | \bestPractice{methodDocstring}{%
|
173 | 207 | All methods of \pythoniles{class} must be annotated with \pglspl{docstring} and \pglspl{typeHint}.%
|
174 | 208 | }%
|
| 209 | +\bestPractice{ownClassTypeHint}{% |
| 210 | +When using a class~\pythonil{C} as \pgls{typeHint} \emph{inside} the definition of the class~\pythonil{C}, you must write~\pythonil{"C"} instead of~\pythonil{C}. % |
| 211 | +(Otherwise, static code analysis tools and the \python\ interpreter get confused.)% |
| 212 | +}% |
175 | 213 | %
|
176 | 214 | We could now go on and add more methods that do reasonable computations with instances of~\pythonil{Point}.
|
177 | 215 | For now, this simple example will suffice.
|
|
189 | 227 | For \pythonil{p1}, this returns~\pythonil{True}.
|
190 | 228 |
|
191 | 229 | We now create a second instance, \pythonil{p2}, of the class \pythonil{Point}.
|
192 |
| -We assign \pythonil{7} to \pythonil{p2.x} and \pythonil{8} to \pythonil{p2.y} via the \pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_} initializer, which is automatically invoked when we write~\pythonil{Point(7, 8)}. |
| 230 | +We assign \pythonil{7} to \pythonil{p2.x} and \pythonil{8} to \pythonil{p2.y} via the \dunder{init} initializer, which is automatically invoked when we write~\pythonil{Point(7, 8)}. |
193 | 231 | We can again print the values of these attributes using an \pgls{fstring}.
|
194 | 232 | While \pythonil{isinstance(p2, Point)} is again \pythonil{True}, \pythonil{isinstance(5, Point)} returns \pythonil{False}.
|
195 | 233 |
|
|
239 | 277 | \endhsection%
|
240 | 278 | %
|
241 | 279 | \hsection{Encapsulation and Accurately Adding Floating Point Numbers}%
|
| 280 | +\FloatBarrier% |
242 | 281 | %
|
243 | 282 | We now want to implement our first maybe actually useful piece of code, something that can be used in a real productive system.\footnote{%
|
244 | 283 | Yes, we did implement LIU Hui's method for approximating~\numberPi\ and Heron's Method for approximating the square root. %
|
|
382 | 421 | %
|
383 | 422 | This is going to be the interface for our new class~\pythonil{KahanSum}, which, in turn, is based on~\cite{K2006AGKBSA}.
|
384 | 423 |
|
385 |
| -In the initializer~\pythonilIdx{\_\_init\_\_}\pythonIdx{dunder!\_\_init\_\_}, we create the three attributes~\pythonil{__sum}, \pythonil{__cs}, and \pythonil{__ccs} and initialize them to~\pythonil{0}. |
| 424 | +In the initializer~\dunder{init}, we create the three attributes~\pythonil{__sum}, \pythonil{__cs}, and \pythonil{__ccs} and initialize them to~\pythonil{0}. |
386 | 425 | These names are directly taken from \cref{algo:kahanSum}.
|
387 | 426 | Notice the double leading underscores in front of the names.%
|
388 | 427 | %
|
|
0 commit comments