|
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