Skip to content

Commit 327912d

Browse files
committed
improved chapter on generator functions
1 parent 20554f2 commit 327912d

File tree

6 files changed

+107
-32
lines changed

6 files changed

+107
-32
lines changed

LICENSE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The list of files not under the CC BY‑NC‑SA 4.0 license bu
1515
+ The illustration of LIU Hui (刘徽) in file "text/main/basics/variables/assignment/liu_hui.jpg" is from Wenqi Ying (应雯棋), editor. Commemoration of Ancient Chinese Mathematical Master Liu Hui for his Timeless Influence on Mathematics and Civilizational Exchange. [Issue 48 of CAST Newsletter](https://english.cast.org.cn/cms_files/filemanager/1941250207/attach/202412/8f23655a82364d19ad7874eb37b23035.pdf). China, Beijing (中国北京市): 中国科学技术协会 (China Association for Science and Technology, CAST), 2024. Permission was granted to include it in this material, but the copyright remains with CAST.
1616
+ The illustration of Hero(n) of Alexandria in file "text/main/controlFlow/loops/heronOfAlexandria.jpg" is taken from the article "[The Ancient Greek Who Invented the World's First Steam Turbine](https://greekreporter.com/2023/12/13/ancient-greek-world-first-steam-turbine)", where its caption states *Heron of Alexandria. Codex of Saint Gregory Nazianzenos. Greek manuscript of the ninth century. Public Domain*.
1717
+ The illustration of Euclid of Alexandria in "text/main/controlFlow/functions/euclidOfAlexandria" [attributed](https://www.antike-griechische.de/Euklid.pdf) to the painter Charles Paul Landon (1760-1826). Source:~[Vikidia](https://fr.vikidia.org/wiki/Cat%C3%A9gorie:Image_Euclide), where it is noted as *domaine public,* i.e., as in the Public Domain.
18+
+ The illustration of Leonardo Pisano / Fibonnaci in file "text/main/controlFlow/iteration/fibonacci.jpg" shows a Sculpture by Bertel Thorvaldsen, 1834/1838 and is sourced from the Thorvaldsens Museum exhibit~[A187](https://kataloget.thorvaldsensmuseum.dk/A187) photographed by Jakob Faurvig, and is published under the Creative Commons Zero ([CC0](https://creativecommons.org/publicdomain/zero/1.0) license.
1819

1920

2021
## creative commons

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ Die Slides zum Kurs in deutscher Sprache können unter <https://thomasweise.gith
5858
33. [Zwischenspiel: Testen auf Ausnahmen](https://thomasweise.github.io/programmingWithPythonSlidesDE/33_testen_auf_ausnahmen.pdf)
5959
34. [Iteration](https://thomasweise.github.io/programmingWithPythonSlidesDE/34_iteration.pdf)
6060
35. [List Comprehension](https://thomasweise.github.io/programmingWithPythonSlidesDE/35_list_comprehension.pdf)
61-
36. [Zwischenspiel: doctests](https://thomasweise.github.io/programmingWithPythonSlidesDE/36_doctests.pdf)
61+
36. [Zwischenspiel: Doctests](https://thomasweise.github.io/programmingWithPythonSlidesDE/36_doctests.pdf)
6262
37. [Set und Dictionary Comprehension](https://thomasweise.github.io/programmingWithPythonSlidesDE/37_set_und_dictionary_comprehension.pdf)
6363
38. [Generator-Ausdrücke](https://thomasweise.github.io/programmingWithPythonSlidesDE/38_generator_ausdrücke.pdf)
64+
39. [Generator-Funktionen](https://thomasweise.github.io/programmingWithPythonSlidesDE/39_generator_funktionen.pdf)
6465

6566

6667
### 2.3. The Slides in English
@@ -99,6 +100,7 @@ The list of files not under the CC&nbsp;BY&#8209;NC&#8209;SA&nbsp;4.0 license bu
99100
+ The illustration of LIU Hui (刘徽) in file "text/main/basics/variables/assignment/liu_hui.jpg" is from Wenqi Ying (应雯棋), editor. Commemoration of Ancient Chinese Mathematical Master Liu Hui for his Timeless Influence on Mathematics and Civilizational Exchange. [Issue 48 of CAST Newsletter](https://english.cast.org.cn/cms_files/filemanager/1941250207/attach/202412/8f23655a82364d19ad7874eb37b23035.pdf). China, Beijing (中国北京市): 中国科学技术协会 (China Association for Science and Technology, CAST), 2024. Permission was granted to include it in this material, but the copyright remains with CAST.
100101
+ The illustration of Hero(n) of Alexandria in file "text/main/controlFlow/loops/heronOfAlexandria.jpg" is taken from the article "[The Ancient Greek Who Invented the World's First Steam Turbine](https://greekreporter.com/2023/12/13/ancient-greek-world-first-steam-turbine)", where its caption states *Heron of Alexandria. Codex of Saint Gregory Nazianzenos. Greek manuscript of the ninth century. Public Domain*.
101102
+ The illustration of Euclid of Alexandria in "text/main/controlFlow/functions/euclidOfAlexandria" [attributed](https://www.antike-griechische.de/Euklid.pdf) to the painter Charles Paul Landon (1760-1826). Source:~[Vikidia](https://fr.vikidia.org/wiki/Cat%C3%A9gorie:Image_Euclide), where it is noted as *domaine public,* i.e., as in the Public Domain.
103+
+ The illustration of Leonardo Pisano / Fibonnaci in file "text/main/controlFlow/iteration/fibonacci.jpg" shows a Sculpture by Bertel Thorvaldsen, 1834/1838 and is sourced from the Thorvaldsens Museum exhibit~[A187](https://kataloget.thorvaldsensmuseum.dk/A187) photographed by Jakob Faurvig, and is published under the Creative Commons Zero&nbsp;([CC0](https://creativecommons.org/publicdomain/zero/1.0) license.
102104

103105
You can download its newest version of the course material from <https://thomasweise.github.io/databases>.
104106
This version may change since this course and book both are work in progress.

bookbase

169 KB
Loading

text/main/controlFlow/iteration/iteration.tex

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -847,80 +847,145 @@
847847
\hsection{Generator Functions}%
848848
\FloatBarrier%
849849
%
850-
\gitLoadAndExecPythonAndErrors{iteration:simple_generator_function_1}{}{iteration}{simple_generator_function_1.py}{}%
851-
\listingPythonAndError{iteration:simple_generator_function_1}{%
850+
\gitLoadAndExecPythonAndErrors{iteration:simple_generator_function}{}{iteration}{simple_generator_function.py}{}%
851+
\listingPythonAndError{iteration:simple_generator_function}{%
852852
A very simple generator function yielding the numbers~1, 2, and~3\pythonIdx{Generator}.}%
853853
%
854-
\gitLoadAndExecPython{iteration:simple_generator_function_2}{}{iteration}{simple_generator_function_2.py}{}%
855-
\listingPythonAndOutput{iteration:simple_generator_function_2}{%
854+
\begin{figure}%
855+
\centering%
856+
\includegraphics[width=0.33333\linewidth]{\currentDir/fibonacci}%
857+
\caption{%
858+
Leonardo Pisano / Fibonnaci. Sculpture by Bertel Thorvaldsen, 1834/1838. %
859+
Source:~Thorvaldsens Museum exhibit~\href{https://kataloget.thorvaldsensmuseum.dk/A187}{A187}, photographer Jakob Faurvig, under the Creative Commons Zero~(\href{https://creativecommons.org/publicdomain/zero/1.0}{CC0}) license.%
860+
}%
861+
\label{fig:fibonacci}%
862+
\end{figure}%
863+
%
864+
\gitLoadAndExecPython{iteration:fibonacci_generator}{}{iteration}{fibonacci_generator.py}{}%
865+
\listingPythonAndOutput{iteration:fibonacci_generator}{%
856866
A generator function yielding the infinite sequence of Fibonacci numbers\pythonIdx{Generator}~\cite{W2024MAWWR:FN,S2022FLAATIMEOLPBOC}.}{}%
857867
%
858868
\gitLoadPython{iteration:prime_generator}{}{iteration/prime_generator.py}{}%
859869
\listingPython{iteration:prime_generator}{%
860870
A generator function yielding the infinite sequence of prime numbers\pythonIdx{Generator}~\cite{W2024MAWWR:PN,CP2005PNACP,R1994PNACMFF}.}%
861871
%
872+
\gitExec{exec:iteration:prime_generator:doctest}{\programmingWithPythonCodeRepo}{.}{_scripts_/pytest_doctest.sh iteration prime_generator.py}%
873+
\listingToolOutput{iteration:prime_generator:doctest}{%
874+
The output of \pytest\ executing the \pglspl{doctest} for the prime number generator function given as \programUrl{lst:iteration:prime_generator} in \cref{lst:iteration:prime_generator}: %
875+
The test succeeded.}%
876+
%
862877
The final element in \cref{fig:iteration} that we did not yet discuss are \emph{generator functions}~\cite{PEP255}.
863-
From the perspective of the user of a generator function, it is a function that returns an \pythonilIdx{Iterator} of values.
878+
From the perspective of the user of a generator function, it is a function behaves like an \pythonilIdx{Iterator} of values.
864879
We can process the sequence of values provided by this \pythonilIdx{Iterator} in exactly the same ways already discussed.
865880
We can iterate over it using a \pythonilIdx{for}~loop.
866881
We can use it a comprehension or pass it to the constructor of a collection, if we want to.
867882

868-
From the perspective of the implementor, however, it looks more like a function that can return values several times.
883+
From the perspective of the implementor, however, a generator function looks more like a function that can return values several times.
869884
Instead of using the \pythonilIdx{return} keyword, this is achieved by using the \pythonilIdx{yield} keyword.
870885
Each element of the sequence that we generate is produced by returning it via~\pythonilIdx{yield}.
871-
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.
886+
This feels like we can define a function that can return a value, which is then processed by some outside code, and then the function \emph{resumes} to return more values.
872887

873888
Since this sounds quite confusing, let's begin by looking at a very simple example.
874-
In \cref{lst:iteration:simple_generator_function_1}, we create a generator which should produce only the values~1, 2, and~3.
889+
In \cref{lst:iteration:simple_generator_function}, we create a generator which should produce only the values~1, 2, and~3.
875890
It is implemented as a function~\pythonil{generator_123}, which is declared with~\pythonilIdx{def} like any normal \python\ function.
876-
The return type is annotated as \pythonil{Generator[int, None, None]}, meaning that this is a generator function which produces~\pythonil{int} values.
891+
The return type is annotated with \pgls{typeHint} \pythonil{Generator[int, None, None]}, meaning that this is a generator function which produces~\pythonil{int} values.
877892
The function body consists only of the three statements \pythonil{yield 1}, \pythonil{yield 2}, and \pythonil{yield 3}\pythonIdx{yield}.
878893

879894
We can use the \pythonilIdx{Generator} returned by this function to populate a \pythonilIdx{list}:
880895
\pythonil{list(generator_123())} results in the list~\pythonil{[1, 2, 3]}.
881-
Of course we can also iterate over the~\pythonilIdx{Generator} like over any normal~\pythonilIdx{Iterator}.
882-
We first set \pythonil{gen = generator_123()}.
896+
Of course we can also manually iterate over the~\pythonilIdx{Generator} in exactly the same way we could iterator over any normal~\pythonilIdx{Iterator}.
897+
We therefore set \pythonil{gen = generator_123()}.
883898
The first time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{1}.
884899
The second time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{2}.
885900
The third time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{3}.
886901
The fourth call to \pythonil{next(gen)}\pythonIdx{next} raises\pythonIdx{raise} a \pythonilIdx{StopIteration}.
887902
This indicates that the end of the sequence is reached.
888903
Indeed, we queried the generator function's result exactly like a normal~\pythonilIdx{Iterator}.
889904

890-
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).
905+
The interesting thing is that the function code is really disrupted by every \pythonilIdx{yield}.
906+
The value returned via \pythonilIdx{yield} is then received by the outside code, which do whatever it likes with it.
907+
The function is resumed after the \pythonilIdx{yield} only when \pythonilIdx{next} is called.
908+
When the control flow reaches the end of the function, the sequence ends as well.
891909
This becomes visible when we create a generator function that returns an infinite sequence.
892910

893-
In \cref{lst:iteration:simple_generator_function_2}, we implement a generator function producing the Fibonacci numbers~\cite{W2024MAWWR:FN,S2022FLAATIMEOLPBOC}.
894-
These numbers follow the sequence~$F_n=F_{n-1} + F_{n-2}$ where~$F_0=0$ and~$F_1=1$.
895-
We therefore define the function \pythonil{fibonacci}, which is annotated to return a \pythonilIdx{Generator}.
911+
Fibonacci, illustrated in \cref{fig:fibonacci}, was a medieval Italian mathematician who lived in the 12th and 13th century~\pgls{CE}~\cite{EOEBEB:F}.
912+
He wrote the book \emph{Liber Abaci}~\cite{S2022FLAATIMEOLPBOC}, which introduced Arabic numbers to Europe.
913+
He is also known for the Fibonacci number sequence, which follow the sequence~$F_n=F_{n-1} + F_{n-2}$ where~$F_0=0$ and~$F_1=1$~\cite{W2024MAWWR:FN,S2022FLAATIMEOLPBOC}.
914+
915+
We want to iterate over this sequence.
916+
The therefore define the function \pythonil{fibonacci}, which is annotated to return a \pythonilIdx{Generator}.
896917
It begins by setting~$\pythonil{i}=F_0=0$ and~$\pythonil{j}=F_1=1$.
897-
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}.
898-
Then, assign \pythonil{i} and \pythonil{j} to \pythonil{j} and \pythonil{i + j}, respectively.
918+
It then begins a \pythonilIdx{while} loop which will never stop, as the loop condition is imply set to~\pythonil{True}.
919+
In this loop, it always yields~\pythonil{i}\pythonIdx{yield}.
920+
921+
This means that the current function execution will be interrupted.
922+
The value of \pythonil{i} is provided to the outside code which iterates over our sequence.
923+
After that code invokes \pythonilIdx{next}, the function resumes.
924+
Then, it assigns \pythonil{j} and \pythonil{i + j} to \pythonil{i} and \pythonil{j}, respectively.
899925
This stores the old value of~\pythonil{j} in~\pythonil{i}.
900-
It also stores the sum of the old \pythonil{i} and \pythonil{j} values in~\pythonil{j}.
901-
In the next loop iteration, \pythonilIdx{yield} will then produce the next Fibonacci number.
926+
This also stores the sum of the old \pythonil{i} and \pythonil{j} values in~\pythonil{j}.
927+
In the next loop iteration, \pythonil{yield i} will then produce the next Fibonacci number.
902928

903-
We can now loop over the \pythonilIdx{Generator} returned by \pythonil{fibonacci()} in a normal \pythonilIdx{for}~loop.
904-
This would result in an endless loop, unless we insert some termination condition.
905-
In our loop, we print the Fibonacci numbers~\pythonil{a} we get, but stop the iteration via~\pythonilIdx{break} once~\pythonil{a > 300}.
929+
We can now loop over the \pythonilIdx{Generator} returned by \pythonil{fibonacci()} with a normal \pythonilIdx{for}~loop.
930+
This would result in an endless loop, unless we insert some additional termination condition.
931+
In our loop, we print the Fibonacci numbers~\pythonil{a} we get.
932+
However, we stop the iteration via~\pythonilIdx{break} once~\pythonil{a > 30}.
906933

907934
It should be mentioned that doing something like \pythonil{list(fibonacci())} would be a very bad idea.
908935
It would attempt to produce an infinitely large list, which would lead to an \pythonilIdx{MemoryError}.
909936

910-
As final example for generator functions, let us wrap our code for enumerating prime numbers~\cite{W2024MAWWR:PN,CP2005PNACP,R1994PNACMFF} from back in \cref{sec:loopsOverSequences} into a generator function in \cref{lst:iteration:prime_generator}.
911-
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.
912-
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.
937+
As final example for generator functions, let us wrap our code for enumerating prime numbers~\cite{W2024MAWWR:PN,CP2005PNACP,R1994PNACMFF} from back in \cref{sec:loopsOverSequences} into a generator function.
938+
We do this in \programUrl{iteration:prime_generator} given as \cref{lst:iteration:prime_generator}.
939+
Back in \cref{lst:loops:for_loop_sequence_primes}, when we produced prime numbers for the last time, we used nested \pythonilIdx{for}~loops.
940+
Back then, the outer loop would iterate at most to~199.
941+
942+
Now we do not need such limit anymore.
943+
We simply assume that whoever will call our prime number enumeration code will stop iterating whenever they have seen sufficiently many primes.
913944
A \pythonil{while True} loop therefore will be more appropriate.
914-
We also do not need to produce a list, so we do not need to store the only even prime number~(2) anywhere.
945+
We also do not need to produce a list, so we do not need to store the only even prime number~2 anywhere.
915946
Instead, we just \pythonil{yield 2}\pythonIdx{yield} at the beginning of our generator and move on.
916-
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.
947+
The last important difference between the old and new code is that, once we confirm a number to be prime, we do not just append it to the list~\pythonilIdx{found} of odd primes, we also need to \pythonilIdx{yield} it.
948+
949+
Apart from this, the function works pretty much the same as last time.
950+
We iterate over the odd numbers \pythonil{candidate} that could be a prime number.
951+
For each \pythonil{candidate}, we test all previously identified prime numbers~\pythonil{check}~(except~2, of course), whether they can divide \pythonil{candidate} with a remainder of zero.
952+
The first time we encounter such a number, we know that \pythonil{candidate} cannot be a prime number.
953+
If no number \pythonil{check} exists that is a divisor of \pythonil{candidate}, then \pythonil{candidate} is prime.%
954+
%
955+
\begin{sloppypar}%
956+
We only need to test values of \pythonil{check} that are less or equal to \pythonil{isqrt(candidate)}, i.e., $\leq\left\lfloor\sqrt{\pythonil{candidate}}\right\rfloor$.
957+
Numbers larger than that do not need to be tested.
958+
They would have resulted in a quotient smaller than \pythonil{check} that would have already been tested.
959+
In summary, our function will find one prime after the other and \pythonil{yield} it.%
960+
\end{sloppypar}%
961+
%
962+
Every time we \pythonilIdx{yield} a value, our function is interrupted.
963+
The first time, this happens when we \pythonil{yield 2}.
964+
Then, it happens in each iteration of the main loop that discovers a prime number.
965+
Every time our function is interrupted like this, the corresponding value is returned to the outside code iterating over our sequence.
966+
While implementing the function, we have no idea what that code does, well, except that it will apply \pythonilIdx{next} to the generator{\dots}
967+
But we also do not need to know what it does.
968+
All we care is that, if it invokes \pythonilIdx{next}, our function will continue with the instruction following right after the \pythonilIdx{yield} and run until it hits the next \pythonilIdx{yield}.
917969

918-
We demonstrate how our generator works with a \pgls{doctest}.
970+
We demonstrate how our generator function works with a \pgls{doctest}.
919971
The test begins by instantiating the \pythonilIdx{Generator} as \pythonil{gen = primes()}.
920972
The first \pythonil{next(gen)}\pythonIdx{next} call is supposed to return~\pythonil{2}.
921973
The second such call shall return~\pythonil{3}, the third one~\pythonil{5}, the fourth one~\pythonil{7}.
922974
The fifth and last \pythonil{next(gen)}\pythonIdx{next} invocation in the \pgls{doctest} should return~\pythonil{11}.
923975
You can use \pytest\ by yourself to check whether the code works as expected\dots%
976+
Well, OK, I tell you:
977+
It does, as shown in \cref{exec:iteration:prime_generator:doctest}.
978+
979+
From the perspective of a user, a generator function works like a generator expression, which, in turn, works like an iterator.
980+
Compared to generator expressions, generator functions are much more powerful.
981+
We can package arbitrarily complex code into such a function.
982+
This code can have one or multiple points where it returns results to the outside caller.
983+
984+
A normal function can have arbitrarily many \pythonilIdx{return} statements.
985+
However, the execution of a normal function is completed and ends when the first \pythonilIdx{return} is executed.
986+
A generator function can have arbitrarily many \pythonilIdx{yield} statements.
987+
Each statement returns a value to the outside caller.
988+
However, the generator function can continue until either its end is reached or until the outside user stops iterating over the sequence it presents.%
924989
\FloatBarrier%
925990
\endhsection%
926991
%

0 commit comments

Comments
 (0)