|
1 | 1 | \hsection{Lists}% |
2 | 2 | \label{sec:lists}% |
3 | 3 | % |
4 | | -A \pythonilIdx{list} is a mutable sequence of objects which can be accessed via their index~\cite{PSF:P3D:TPLR:S}. |
5 | | -They work very similar to the strings we already discussed in \cref{sec:str}, but instead of characters, they can contain any kind of objects and they can be modified.% |
6 | | -% |
7 | | -\hsection{Basic Functionality and Examples}% |
8 | | -\label{sec:lists:basicFunctions}% |
9 | | -% |
10 | 4 | \gitLoadAndExecPython{lists:lists_1}{}{collections}{lists_1.py}{}% |
11 | 5 | \listingPythonAndOutput{lists:lists_1}{% |
12 | 6 | A first example for using lists in \python:~creating, indexing, printing of and appending elements and other lists to lists.}{}% |
|
19 | 13 | \listingPythonAndOutput{lists:lists_3}{% |
20 | 14 | A third example of using lists in \python: slicing, adding, and multiplying lists.}{}% |
21 | 15 | % |
| 16 | +A \pythonilIdx{list} is a mutable sequence of objects which can be accessed via their index~\cite{PSF:P3D:TPLR:S}. |
| 17 | +They work very similar to the strings we already discussed in \cref{sec:str}, but instead of (only) characters, they can contain any kind of objects and they can be modified. |
| 18 | + |
22 | 19 | In \cref{lst:lists:lists_1}, we provide some first examples for using lists. |
23 | 20 | A list can be defined by simply writing its contents, separated by~\pythonilIdx{,} inside square brackets~\pythonil{[...]}\pythonIdx{[\idxdots]}. |
24 | 21 | \pythonil{["apple", "pear", "orange"]} creates a list with three elements, namely the strings \pythonil{"apple"}, \pythonil{"pear"}, and~\pythonil{"orange"}. |
|
117 | 114 | If~\pythonil{i} is omitted, i.e., if \pythonil{:j:k} is provided, then \pythonil{i=0} is assumed. |
118 | 115 | If~\pythonil{j} is omitted, i.e., if \pythonil{i::k} is provided, then \pythonil{j=len(l)} is assumed. |
119 | 116 |
|
120 | | -If you have a list \pythonil{l4 = [5, 6, 7, 5, 6, 7, 5, 6, 7]}, then the slice~\pythonil{l4[2:-2]} will return a new list which contains all the elements of~\pythonil{l4} starting from index~\pythonil{2} and up to (excluding) the second-to-last element. |
121 | | -The slice~\pythonil{l4[1::2]} starts at index~\pythonil{1}, continues until the end of the list, and adds every second element. |
| 117 | +If you have a list \pythonil{lst4 = [5, 6, 7, 5, 6, 7, 5, 6, 7]}, then the slice~\pythonil{lst4[2:-2]} will return a new list which contains all the elements of~\pythonil{lst4} starting from index~\pythonil{2} and up to (excluding) the second-to-last element. |
| 118 | +The slice~\pythonil{lst4[1::2]} starts at index~\pythonil{1}, continues until the end of the list, and adds every second element. |
122 | 119 | This results in~\pythonil{[6, 5, 7, 6]}. |
123 | | -As final example, consider the slice~\pythonil{l4[-1:3:-2]}. |
| 120 | +As final example, consider the slice~\pythonil{lst4[-1:3:-2]}. |
124 | 121 | It will begin creating the new list at the last element. |
125 | 122 | The step-length is~\pythonil{-2}, so it will move backwards and add every second element to the new list. |
126 | 123 | It stops adding elements before reaching index~\pythonil{3}. |
127 | | -Therefore, the result will be the new list~\pythonil{l7 = [7, 5, 6]}. |
| 124 | +Therefore, the result will be the new list~\pythonil{lst7 = [7, 5, 6]}. |
128 | 125 |
|
129 | 126 | Notice that the slices\pythonIdx{slice} we create are independent copies of ranges of the original lists. |
130 | | -The list~\pythonil{l7} is a slice from the list~\pythonil{l4}. |
131 | | -If we modify it, e.g., set \pythonil{l7[1] = 12}, then we set the second element of~\pythonil{l7} to~\pythonil{12}. |
132 | | -\pythonil{l7}~becomes~\pythonil{[7, 12, 6]}. |
133 | | -Now, the second element of~\pythonil{l7} originally is the seventh element of~\pythonil{l4}, namely the \pythonil{5} located at index~\pythonil{6}, which is equivalent to index~\pythonil{-3}. |
| 127 | +The list~\pythonil{lst7} is a slice from the list~\pythonil{lst4}. |
| 128 | +If we modify it, e.g., set \pythonil{lst7[1] = 12}, then we set the second element of~\pythonil{lst7} to~\pythonil{12}. |
| 129 | +\pythonil{lst7}~becomes~\pythonil{[7, 12, 6]}. |
| 130 | +Now, the second element of~\pythonil{lst7} originally is the seventh element of~\pythonil{lst4}, namely the \pythonil{5} located at index~\pythonil{6}, which is equivalent to index~\pythonil{-3}. |
134 | 131 | You may wonder whether this element now also has changed. |
135 | 132 | It did not. |
136 | | -\pythonil{l4} remains unchanged by any operation on the independent copied slice~\pythonil{l7}. |
| 133 | +\pythonil{lst4} remains unchanged by any operation on the independent copied slice~\pythonil{lst7}. |
137 | 134 |
|
138 | 135 | An interesting functionality is also list unpacking\pythonIdx{list!unpacking}\pythonIdx{unpacking}. |
139 | | -In \cref{sec:strBasicOperations}, the list~\pythonil{l2} contains the three elements~\pythonil{[5, 6, 7]}. |
| 136 | +In \cref{sec:strBasicOperations}, the list~\pythonil{lst2} contains the three elements~\pythonil{[5, 6, 7]}. |
140 | 137 | If we know the number of elements in the list in our program, then we can assign them to exactly the same number of variables. |
141 | | -\pythonil{a, b, c = l2} creates and assigns values to three variables \pythonil{a=5}, \pythonil{b=6}, and \pythonil{c=7} by unpacking the list~\pythonil{l2}.% |
142 | | -% |
143 | | -\FloatBarrier% |
144 | | -\endhsection% |
145 | | -% |
146 | | -\hsection{An Example of Errors and a new Tool}% |
147 | | -\label{sec:listExampleForErrorsAndRuff}% |
148 | | -% |
149 | | -\gitLoadAndExecPython{lists:lists_error}{}{collections}{lists_error.py}{}% |
150 | | -\listingPythonAndOutput{lists:lists_error}{% |
151 | | -A program processing lists which exhibits some subtle errors and inefficiencies.}{}% |
152 | | -% |
153 | | -Now, in the previous chapter, we learned that static code analysis tools can help us to discover subtle problems in our programs. |
154 | | -Obviously, when dealing with more complex datastructures like lists, there are also more potential problems, more mistakes that one could make. |
155 | | -Let us look at the very short example \cref{lst:lists:lists_error}. |
156 | | -The program consists of only two lines, \pythonil{my_list: list[str] = list([1, 2, 3])} and \pythonil{print(my_list)}. |
157 | | -It does not have any \emph{error} in the strict sense. |
158 | | -We can execute it just fine and it will produce the output \textil{[1, 2, 3]} as shown in \cref{exec:lists:lists_error}. |
159 | | - |
160 | | -\gitExec{exec:lists:lists_error:mypy}{\programmingWithPythonCodeRepo}{.}{_scripts_/mypy.sh collections lists_error.py}% |
161 | | -\listingToolOutput{lists:lists_error:mypy}{% |
162 | | -The results of static type checking with \mypy\ of the program given in \cref{lst:lists:lists_error}.} |
163 | | - |
164 | | -However, upon closer inspection, we discover some issues. |
165 | | -In a first step, we would apply \mypy~(as~\cref{ut:mypy}) to check for problems with the types of variables. |
166 | | -And indeed, \cref{exec:lists:lists_error:mypy} shows us \emph{three} errors! |
167 | | -We defined \pythonil{my_list} as a list of strings by using the \pgls{typeHint} \pythonil{list[str]}. |
168 | | -However, we then set its value to be a list of three integer numbers (hence, three errors). |
169 | | -As promised in the title of this section, we will also use another tool to analyze this program:~\ruff. |
170 | | - |
171 | | -\begin{figure}% |
172 | | -\centering% |
173 | | -\includegraphics[width=0.7\linewidth]{\currentDir/pipInstallRuff}% |
174 | | -\caption{Installing \ruff\ in a \ubuntu\ \pgls{terminal} via \pip~(see \cref{sec:pipAndVenv} for a discussion of how packages can be installed).}% |
175 | | -\label{fig:pipInstallRuff}% |
176 | | -\end{figure}% |
| 138 | +\pythonil{a, b, c = lst2} creates and assigns values to three variables \pythonil{a=5}, \pythonil{b=6}, and \pythonil{c=7} by unpacking the list~\pythonil{lst2}. |
| 139 | +Of course, this only works if list~\pythonil{lst2} has exactly length~3.% |
177 | 140 | % |
178 | 141 | \FloatBarrier% |
179 | | -% |
180 | | -\usefulTool{ruff}{% |
181 | | -\ruff\ is a very fast \python\ \pgls{linter} that checks the code for all kinds of problems, ranging from formatting and style issues over missing documentation to performance problems and potential errors~\cite{M2022RAEFPLACFWIR}. % |
182 | | -It can be installed via \bashil{pip install ruff} as shown in \cref{fig:pipInstallRuff} on \cpageref{fig:pipInstallRuff}. % |
183 | | -You can then apply \ruff\ using the command \bashil{ruff check fileToScan.py}. % |
184 | | -We provide a script for using \ruff\ with a reasonable default configuration in \cref{lst:bash:ruff} on \cpageref{lst:bash:ruff}.% |
185 | | -}% |
186 | | -% |
187 | | -\gitExec{exec:lists:lists_error:ruff}{\programmingWithPythonCodeRepo}{.}{_scripts_/ruff.sh collections lists_error.py}% |
188 | | -\listingToolOutput{lists:lists_error:ruff}{% |
189 | | -The results of linting with \ruff\ of the program given in \cref{lst:lists:lists_error}. (We used the script given in \cref{lst:bash:ruff} on \cpageref{lst:bash:ruff} to apply \ruff.)}% |
190 | | -% |
191 | | -Let us apply \ruff\ to the program \textil{lists_error.py} given in \cref{lst:lists:lists_error}, which produces the output \cref{exec:lists:lists_error:ruff}. |
192 | | -\ruff\ finds two errors in this file: |
193 | | -First, it complains that any python file should start with multi-line string specifying the purpose of the file. |
194 | | -The use of such \pglspl{docstring} makes it easier for other programmers to understand what is done by which file in projects that are composed of multiple \python\ scripts.% |
195 | | -% |
196 | | -\bestPractice{module:docstrings}{ |
197 | | -Each \python\ file should start with a string describing its purpose~\cite{PEP257}. % |
198 | | -This can either be a single line, like a headline, or a longer text. % |
199 | | -In the second case, the first line must be a headline, followed by an empty line, followed by the rest of the text. % |
200 | | -Either way, it must be a string delimited by~\pythonil{"""..."""}\pythonIdx{\textquotedbl\textquotedbl\textquotedbl\idxdots\textquotedbl\textquotedbl\textquotedbl}~\cite{PEP257,PEP8}.% |
201 | | -\pythonIdx{str!doc}% |
202 | | -}% |
203 | | -% |
204 | | -Additionally, \ruff\ finds that writing \pythonil{list([1, 2, 3])} is actually useless waste of speed and memory: |
205 | | -It basically creates a list via \pythonil{[1, 2, 3]} and then immediately makes a copy of it via the \pythonilIdx{list} function wrapped around the list specification. |
206 | | -We can leave this outer call to \pythonil{list} away. |
207 | | - |
208 | | -\gitLoadAndExecPython{lists:lists_fixed}{}{collections}{lists_fixed.py}{}% |
209 | | -\listingPythonAndOutput{lists:lists_fixed}{% |
210 | | -The corrected version of~\cref{lst:lists:lists_error}, taking into account the information given by \mypy\ in \cref{exec:lists:lists_error:mypy} and \ruff\ in \cref{exec:lists:lists_error:ruff}.}{}% |
211 | | -% |
212 | | -% |
213 | | -In \cref{lst:lists:lists_fixed} we implement the three recommendations from the two tools. |
214 | | -We change the \pgls{typeHint} of the list to \pythonil{list[int]}, which solves the type confusion that \mypy\ discovered. |
215 | | -We remove the useless copying of the list as \ruff\ recommended. |
216 | | -Finally, we add a proper \pgls{docstring} at the top of the file, in which we even document the changes we applied. |
217 | | -The output \cref{exec:lists:lists_fixed} of the new program remains the same. |
218 | | -But now, both tools are satisfied, as shown in \cref{exec:lists:lists_fixed:mypy,exec:lists:lists_fixed:ruff}. |
219 | | -And our program is much clearer and faster.% |
220 | | -% |
221 | | -\bestPractice{manyCodeAnalysisTools}{% |
222 | | -Use many static code analysis tools and use them always. % |
223 | | -They can discover a wide variety of issues, problems, or potential improvements. % |
224 | | -They can help you to keep your code clean and to enforce a good programming style. % |
225 | | -Do not just apply them, but also \emph{implement} their suggestions where possible.% |
226 | | -}% |
227 | | -% |
228 | | -\FloatBarrier% |
229 | | -% |
230 | | -\gitExec{exec:lists:lists_fixed:mypy}{\programmingWithPythonCodeRepo}{.}{_scripts_/mypy.sh collections lists_fixed.py}% |
231 | | -\listingToolOutput{lists:lists_fixed:mypy}{% |
232 | | -The results of static type checking with \mypy\ of the program given in \cref{lst:lists:lists_fixed}.}% |
233 | | -% |
234 | | -\gitExec{exec:lists:lists_fixed:ruff}{\programmingWithPythonCodeRepo}{.}{_scripts_/ruff.sh collections lists_fixed.py}% |
235 | | -\listingToolOutput{lists:lists_fixed:ruff}{% |
236 | | -The results of static type checking with \ruff\ of the program given in \cref{lst:lists:lists_fixed}.} |
237 | | - |
238 | | -Well, this was only a two-line program. |
239 | | -But ask yourself: |
240 | | -Did you spot the incorrect \pgls{typeHint} when you read the program? |
241 | | -Did you see that we actually created a list and then copied it instead of using it directly? |
242 | | -(The \pgls{docstring} I give you, no chance of seeing that as we did not mention it before.) |
243 | | -Imagine that your job would be to work on a program with thousands of lines that was developed by a colleague. |
244 | | -Wouldn't you love it if that colleague had thoroughly documented and type-hinted and checked their code? |
245 | | -Be that colleague.% |
246 | | -% |
247 | | -\FloatBarrier% |
248 | | -\endhsection% |
249 | 142 | \endhsection% |
250 | 143 | % |
0 commit comments