Skip to content

Commit 3bd81ca

Browse files
committed
some improvements
1 parent 5807343 commit 3bd81ca

File tree

4 files changed

+132
-80
lines changed

4 files changed

+132
-80
lines changed

bibliography/bibliography.bib

+10
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,16 @@ @book{H2023ABGTP3P
17781778
isbn = {978-3-031-35121-1},
17791779
}
17801780

1781+
@book{H2024EDMIP,
1782+
author = a_hunner_trey,
1783+
title = {Every Dunder\pythonIdx{dunder} Method in \python},
1784+
date = {2024-03-19},
1785+
publisher = p_python_morsels,
1786+
address = pa_python_morsels,
1787+
url = {https://www.pythonmorsels.com/every-dunder-method},
1788+
urldate = {2024-12-18},
1789+
}
1790+
17811791
@book{H2024PBOTTCODDSIPP33,
17821792
author = a_hunner_trey,
17831793
title = {\python\ Big~\bigO:~{T}he Time Complexities of Different Data Structures in \python; \python~3.8\nobreakdashes-3.12},

text/main/classes/basics/basics.tex

+55-16
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,24 @@
114114
%
115115
Our class \pythonil{Point} will have two attributes, \pythonil{x} and~\pythonil{y}.
116116
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.
118119

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}.
121122
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}.
123124
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.%
125132
%
126133
\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}. %
128135
An initial value must immediately be assigned to each attribute.%
129136
}
130137
%
@@ -143,35 +150,66 @@
143150
In other words, we do not allow the coordinates of our \pythonils{Point} to change after object creation.%
144151
%
145152
\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}. %
147154
\pglspl{typeHint} work as with normal variables.}%
148155
%
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+
%
149161
\bestPractice{attributeDocstring}{%
150162
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}. %
151163
(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.)%
152164
}%
153165
%
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.
158174
There is no way to mistake the meaning of these variables.
159175
Of course, our \pglspl{docstring} with \pglspl{doctest} and \pglspl{typeHint} further help the reader to understand their meaning.
160176

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.
162178
But \pythoniles{class} also allow us to define operations on such points in form of methods.
163179
As an example, we implement a method \pythonil{distance} that computes the distance between two points.
164180
You would have a point~\pythonil{p1} and invoke \pythonil{p1.distance(p2)} to compute the distance to another point~\pythonil{p2}.
165181
The computation itself will follow \cref{eq:euclideanDistance} from our recent endeavor to operations on iterations in \cref{sec:operationsOnIterators}.
166182
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).
168185
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}.
170190
If you compute \pythonil{Point(1, 1).distance(Point(4, 4))}, then the expected result is something like~4.243.%
171191
%
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+
%
172206
\bestPractice{methodDocstring}{%
173207
All methods of \pythoniles{class} must be annotated with \pglspl{docstring} and \pglspl{typeHint}.%
174208
}%
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+
}%
175213
%
176214
We could now go on and add more methods that do reasonable computations with instances of~\pythonil{Point}.
177215
For now, this simple example will suffice.
@@ -189,7 +227,7 @@
189227
For \pythonil{p1}, this returns~\pythonil{True}.
190228

191229
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)}.
193231
We can again print the values of these attributes using an \pgls{fstring}.
194232
While \pythonil{isinstance(p2, Point)} is again \pythonil{True}, \pythonil{isinstance(5, Point)} returns \pythonil{False}.
195233

@@ -239,6 +277,7 @@
239277
\endhsection%
240278
%
241279
\hsection{Encapsulation and Accurately Adding Floating Point Numbers}%
280+
\FloatBarrier%
242281
%
243282
We now want to implement our first maybe actually useful piece of code, something that can be used in a real productive system.\footnote{%
244283
Yes, we did implement LIU Hui's method for approximating~\numberPi\ and Heron's Method for approximating the square root. %
@@ -382,7 +421,7 @@
382421
%
383422
This is going to be the interface for our new class~\pythonil{KahanSum}, which, in turn, is based on~\cite{K2006AGKBSA}.
384423

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}.
386425
These names are directly taken from \cref{algo:kahanSum}.
387426
Notice the double leading underscores in front of the names.%
388427
%

0 commit comments

Comments
 (0)