|
57 | 57 | %
|
58 | 58 | \begin{sloppypar}%
|
59 | 59 | Any object that allows us to access its elements one-by-one, i.e., \emph{iteratively} is an instance of \pythonilIdx{typing.Iterable}\pythonIdx{Iterable}.
|
60 |
| -The actual iteration over the contents is then done by an \pythonilIdx{typing.Iterator}\pythonIdx{Iterator}. |
| 60 | +The actual iteration over the contents is then done by an \pythonilIdx{typing.Iterator}\pythonIdx{Iterator}~\cite{PEP234}. |
61 | 61 | This distinction is necessary because we want to allow some objects to be iterated over multiple times.%
|
62 | 62 | \end{sloppypar}%
|
63 | 63 | %
|
|
287 | 287 | A \pgls{doctest} is a unit test written directly into the \pgls{docstring} of a function or module. %
|
288 | 288 | We therefore insert small snippets of \python\ code and their expected output. %
|
289 | 289 | The first line of such codes is prefixed py~\pythonil{>>>}\pythonIdx{>\strut>\strut>}. %
|
290 |
| -If the snipped has multiple lines, any following line is prefixed by~\pythonil{...}\pythonIdx{\idxdots}. % |
| 290 | +If a statement needs multiple lines, any following line is prefixed by~\pythonil{...}\pythonIdx{\idxdots}. % |
291 | 291 | After the snippet, the expected output is written. %
|
292 | 292 | The \pglspl{doctest} can be by modules like \pythonilIdx{doctest}~\cite{PSF2024DTIPE} or tools such as \pytest~\cite{KPDT2024HTRD}~(\cref{ut:pytest}). %
|
293 | 293 | They collect the code, run it, and compare its output to the expected output in the \pgls{docstring}. %
|
|
356 | 356 | \end{sloppypar}%
|
357 | 357 | %
|
358 | 358 | Let us now try do something more interesting:
|
359 |
| -We want to create the set \pythonil{primes} of the prime numbers in the range~2 to~99. |
| 359 | +We want to create the set \pythonil{primes} of the prime numbers~\cite{W2024PN,CP2005PNACP,R1994PNACMFF} in the range~2 to~99. |
360 | 360 | We already created a beautiful program doing this efficiently in \cref{lst:loops:for_loop_sequence_primes}.
|
361 | 361 | This time, we will use set comprehension\pythonIdx{set!comprehension}\pythonIdx{comprehension!set}.
|
362 | 362 |
|
|
591 | 591 | We could have just iterated over the \pythonilIdx{range} directly, which would have been even more efficient {\dots} but then we could not have used this as an example.%
|
592 | 592 | %
|
593 | 593 | \gitPythonAndOutput{\programmingWithPythonCodeRepo}{07_iteration}{generator_expressions_to_collection.py}{--args format}{iteration:generator_expressions_to_collection}{%
|
594 |
| -Using generator expressions when creating collection datastructures\pythonilIdx{Generator}\pythonilIdx{list}\pythonilIdx{dict}.}% |
| 594 | +Using generator expressions when creating collection datastructures\pythonIdx{Generator}\pythonIdx{list}\pythonIdx{dict}.}% |
595 | 595 | %
|
596 | 596 | \FloatBarrier%
|
597 | 597 | %
|
|
625 | 625 | \FloatBarrier%
|
626 | 626 | \endhsection%
|
627 | 627 | %
|
| 628 | +\hsection{Generator Functions}% |
| 629 | +% |
| 630 | +\gitPythonAndErrorOutput{\programmingWithPythonCodeRepo}{07_iteration}{simple_generator_function_1.py}{--args format}{iteration:simple_generator_function_1}{% |
| 631 | +A very simple generator function yielding the numbers~1, 2, and~3\pythonIdx{Generator}.}% |
| 632 | +% |
| 633 | +\gitPythonAndOutput{\programmingWithPythonCodeRepo}{07_iteration}{simple_generator_function_2.py}{--args format}{iteration:simple_generator_function_2}{% |
| 634 | +A generator function yielding the infinite sequence of Fibonacci numbers\pythonIdx{Generator}~\cite{W2024FN,S2022FLAATIMEOLPBOC}.}% |
| 635 | +% |
| 636 | +\gitPython{\programmingWithPythonCodeRepo}{07_iteration/prime_generator.py}{--args format}{iteration:prime_generator}{% |
| 637 | +A generator function yielding the infinite sequence of prime numbers\pythonIdx{Generator}~\cite{W2024PN,CP2005PNACP,R1994PNACMFF}.}% |
| 638 | +% |
| 639 | +The final element in \cref{fig:iteration} that we did not yet discuss are \emph{generator functions}~\cite{PEP255}. |
| 640 | +From the perspective of the user of a generator function, it is a function that returns an \pythonilIdx{Iterator} of values. |
| 641 | +We can process the sequence of values provided by this \pythonilIdx{Iterator} in exactly the same ways already discussed. |
| 642 | +We can iterate over it using a \pythonilIdx{for}~loop. |
| 643 | +We can use it a comprehension or pass it to the constructor of a collection, if we want to. |
| 644 | + |
| 645 | +From the perspective of the implementor, however, it looks more like a function that can return values several times. |
| 646 | +Instead of using the \pythonilIdx{return} keyword, this is achieved by using the \pythonilIdx{yield} keyword. |
| 647 | +Each element of the sequence that we generate is produced by returning it via~\pythonilIdx{yield}. |
| 648 | +This has the feel of a function that can return a value, which is then processed by some outside code, and then the function resumes to return more values. |
| 649 | + |
| 650 | +Since this sounds quite confusing, let's begin by looking at a very simple example. |
| 651 | +In \cref{lst:iteration:simple_generator_function_1}, we create a generator which should produce only the values~1, 2, and~3. |
| 652 | +It is implemented as a function~\pythonil{generator_123}, which is declared with~\pythonilIdx{def} like any normal \python\ function. |
| 653 | +The return type is annotated as \pythonil{Generator[int, None, None]}, meaning that this is a generator function which produces~\pythonil{int} values. |
| 654 | +The function body consists only of the three statements \pythonil{yield 1}, \pythonil{yield 2}, and \pythonil{yield 3}\pythonIdx{yield}. |
| 655 | + |
| 656 | +We can use the \pythonilIdx{Generator} returned by this function to populate a \pythonilIdx{list}: |
| 657 | +\pythonil{list(generator_123())} results in the list~\pythonil{[1, 2, 3]}. |
| 658 | +Of course we can also iterate over the~\pythonilIdx{Generator} like over any normal~\pythonilIdx{Iterator}. |
| 659 | +We first set \pythonil{gen = generator_123()}. |
| 660 | +The first time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{1}. |
| 661 | +The second time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{2}. |
| 662 | +The third time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{3}. |
| 663 | +The fourth call to \pythonil{next(gen)}\pythonIdx{next} raises\pythonIdx{raise} a \pythonilIdx{StopIteration}. |
| 664 | +This indicates that the end of the sequence is reached. |
| 665 | +Indeed, we queried the generator function's result exactly like a normal~\pythonilIdx{Iterator}. |
| 666 | + |
| 667 | +The interesting thing is that the function code is really disrupted by every \pythonilIdx{yield} and resumed when \pythonilIdx{next} is called (unless the sequence was finished, that is). |
| 668 | +This becomes visible when we create a generator function that returns an infinite sequence. |
| 669 | + |
| 670 | +In \cref{lst:iteration:simple_generator_function_2}, we implement a generator function producing the Fibonacci numbers~\cite{W2024FN,S2022FLAATIMEOLPBOC}. |
| 671 | +These numbers follow the sequence~$F_n=F_{n-1} + F_{n-2}$ where~$F_0=0$ and~$F_1=1$. |
| 672 | +We therefore define the function \pythonil{fibonacci}, which is annotated to return a \pythonilIdx{Generator}. |
| 673 | +It begins by setting~$\pythonil{i}=F_0=0$ and~$\pythonil{j}=F_1=1$. |
| 674 | +In a \pythonilIdx{while} loop which will never stop (as the loop condition is imply set to~\pythonil{True}), it always yields~\pythonil{i}\pythonIdx{yield}. |
| 675 | +Then, assign \pythonil{i} and \pythonil{j} to \pythonil{j} and \pythonil{i + j}, respectively. |
| 676 | +This stores the old value of~\pythonil{j} in~\pythonil{i}. |
| 677 | +It also stores the sum of the old \pythonil{i} and \pythonil{j} values in~\pythonil{j}. |
| 678 | +In the next loop iteration, \pythonilIdx{yield} will then produce the next Fibonacci number. |
| 679 | + |
| 680 | +We can now loop over the \pythonilIdx{Generator} returned by \pythonil{fibonacci()} in a normal \pythonilIdx{for}~loop. |
| 681 | +This would result in an endless loop, unless we insert some termination condition. |
| 682 | +In our loop, we print the Fibonacci numbers~\pythonil{a} we get, but stop the iteration via~\pythonilIdx{break} once~\pythonil{a > 300}. |
| 683 | + |
| 684 | +It should be mentioned that doing something like \pythonil{list(fibonacci())} would be a very bad idea. |
| 685 | +It would attempt to produce an infinitely large list, which would lead to an \pythonilIdx{MemoryError}. |
| 686 | + |
| 687 | +As final example for generator functions, let us wrap our code for enumerating prime numbers~\cite{W2024PN,CP2005PNACP,R1994PNACMFF} from back in \cref{sec:loopsOverSequences} into a generator function in \cref{lst:iteration:prime_generator}. |
| 688 | +We used nested \pythonilIdx{for}~loop to produce the prime numbers in \cref{lst:loops:for_loop_sequence_primes}, where the outer loop would iterate at most to~199. |
| 689 | +We do not need such limit anymore, as we can assume that whoever will call our prime number enumeration code will stop iterating whenever they have seen sufficiently many primes. |
| 690 | +A \pythonil{while True} loop therefore will be more appropriate. |
| 691 | +We also do not need to produce a list, so we do not need to store the only even prime number~(2) anywhere. |
| 692 | +Instead, we just \pythonil{yield 2}\pythonIdx{yield} at the beginning of our generator and move on. |
| 693 | +The last difference between the old and new code is that, once we confirm a number to be prime, we do not just add it to the list~\pythonilIdx{found} of odd primes, we also need to \pythonilIdx{yield} it. |
| 694 | + |
| 695 | +We demonstrate how our generator works with a \pgls{doctest}. |
| 696 | +The test begins by instantiating the \pythonilIdx{Generator} as \pythonil{gen = primes()}. |
| 697 | +The first \pythonil{next(gen)}\pythonIdx{next} call is supposed to return~\pythonil{2}. |
| 698 | +The second such call shall return~\pythonil{3}, the third one~\pythonil{5}, the fourth one~\pythonil{7}. |
| 699 | +The fifth and last \pythonil{next(gen)}\pythonIdx{next} invocation in the \pgls{doctest} should return~\pythonil{11}. |
| 700 | +You can use \pytest\ by yourself to check whether the code works as expected\dots% |
| 701 | +\FloatBarrier% |
| 702 | +\endhsection% |
| 703 | +% |
628 | 704 | \endhsection%
|
629 | 705 | %
|
0 commit comments