Skip to content

Commit 78f7354

Browse files
committed
added generator functions
1 parent ad40d6d commit 78f7354

File tree

3 files changed

+128
-5
lines changed

3 files changed

+128
-5
lines changed

bibliography/bibliography.bib

+47
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ @string { a_heap_danny
142142
@string { a_heiberg_johan_ludvig = "Johan Ludvig Heiberg" }
143143
@string { a_henriksen_ian = "Ian Henriksen" }
144144
@string { a_herbsleb_james_d = "James D.\ Herbsleb" }
145+
@string { a_hetland_magnus_lie = "Magnus Lie Hetland" }
145146
@string { a_hettinger_raymond = "Raymond Hettinger" }
146147
@string { a_hollasch_steve = "Steve Hollasch" }
147148
@string { a_hoyer_stephan = "Stephan Hoyer" }
@@ -281,6 +282,7 @@ @string { a_sachsenberg_timo
281282
@string { a_sagher_yoram = "Yoram Sagher" }
282283
@string { a_salakoski_tapio = "Tapio Salakoski" }
283284
@string { a_salvaneschi_guido = "Guido Salvaneschi" }
285+
@string { a_schemenauer_neil = "Neil Schemenauer" }
284286
@string { a_schurr_andy = "Andy Sch{\"u}rr" }
285287
@string { a_scipy_1 = "{{SciPy~1.0 Contributors}}" }
286288
@string { a_scott_larkin_rigdway = "Larkin Ridgway Scott" }
@@ -289,6 +291,7 @@ @string { a_shalev_shwartz_shai
289291
@string { a_shahrokni_ali = "Ali Shahrokni" }
290292
@string { a_shen_kangshen = "Shen Kangshen" }
291293
@string { a_sheppart_kevin = "Kevin Sheppard" }
294+
@string { a_sigler_laurence_e = "Laurence E.\ Sigler" }
292295
@string { a_silberschatz_avi = "Avi Silberschatz" }
293296
@string { a_silva_sara = "Sara Silva" }
294297
@string { a_skoulikari_anna = "Anna Skoulikari" }
@@ -353,6 +356,7 @@ @string { a_yang_edward_z
353356
@string { a_yang_joonmo = "Joonmo Yang" }
354357
@string { a_yang_xuanda = "Xuanda Yang" }
355358
@string { a_ye_jingchen = "Jingchen Ye" }
359+
@string { a_yee_ka_ping = "Ka{-}Ping Yee" }
356360
@string { a_yu_yuan = "Yuan Yu" }
357361
@string { a_zarrelli_giorgio = "Giorgio Zarrelli" }
358362
@string { a_zheng_xiaoqiang = "Xiaoqiang Zheng" }
@@ -817,6 +821,13 @@ @xdata{ser_pm
817821
address = pa_springer_science_and_business
818822
}
819823

824+
@xdata{ser_sasithomaps,
825+
series = {Sources and Studies in the History of Mathematics and Physical Sciences},
826+
issn = {2196-8810},
827+
publisher = p_springer_new_york,
828+
address = pa_springer_new_york,
829+
}
830+
820831
@xdata{ser_sbics,
821832
series = {{SpringerBriefs} in Computer Science},
822833
publisher = p_springer_cham,
@@ -1795,6 +1806,26 @@ @techreport{PEP202
17951806
urldate = {2024-11-08}
17961807
}
17971808

1809+
@techreport{PEP234,
1810+
author = a_yee_ka_ping # and # a_van_rossum_guido,
1811+
title = {Iterators},
1812+
xdata = {rep_pep},
1813+
number = {234},
1814+
date = {2001-05-18},
1815+
url = {https://peps.python.org/pep-0234},
1816+
urldate = {2001-01-30}
1817+
}
1818+
1819+
@techreport{PEP255,
1820+
author = a_schemenauer_neil # and # a_peters_tim # and # a_hetland_magnus_lie,
1821+
title = {Simple Generators},
1822+
xdata = {rep_pep},
1823+
number = {255},
1824+
date = {2001-05-18},
1825+
url = {https://peps.python.org/pep-0255},
1826+
urldate = {2024-11-08}
1827+
}
1828+
17981829
@techreport{PEP257,
17991830
title = {\Pgls{docstring} Conventions},
18001831
author = a_goodger_david # and # a_van_rossum_guido,
@@ -2321,6 +2352,15 @@ @article{S1998LHATFGAOCM
23212352
doi = {10.2307/2691200},
23222353
}
23232354

2355+
@book{S2022FLAATIMEOLPBOC,
2356+
author = a_sigler_laurence_e,
2357+
title = {Fibonacci's Liber Abaci:~{A} Translation into Modern English of Leonardo Pisano's Book of Calculation},
2358+
xdata = {ser_sasithomaps},
2359+
isbn = {978-0-387-95419-6},
2360+
doi = {10.1007/978-1-4613-0079-3},
2361+
date = {2002-09-10},
2362+
}
2363+
23242364
@book{S2011NA,
23252365
author = a_scott_larkin_rigdway,
23262366
title = {Numerical Analysis},
@@ -2540,6 +2580,13 @@ @book{W2024MAWWR
25402580
urldate = {2024-09-24},
25412581
}
25422582

2583+
@inbook{W2024FN,
2584+
title = {Fibonacci Number},
2585+
crossref = {W2024MAWWR},
2586+
url = {https://mathworld.wolfram.com/FibonacciNumber.html},
2587+
urldate = {2024-11-08},
2588+
}
2589+
25432590
@inbook{W2024PN,
25442591
title = {Prime Number},
25452592
crossref = {W2024MAWWR},

notation/terms.sty

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ is another programming language, which is very successful in system programming
3737
name={doctest},%
3838
description={%
3939
\emph{doctests} are small pieces of code in the \pglspl{docstring} that look like interactive \python\ sessions.
40-
The first line of such a \python\ snippet is indented with \python{>>>}\pythonIdx{>\strut>\strut>} and the following lines by \pythonil{...}\pythonIdx{\idxdots}.
40+
The first line of a statement in such a \python\ snippet is indented with \python{>>>}\pythonIdx{>\strut>\strut>} and the following lines by \pythonil{...}\pythonIdx{\idxdots}.
4141
These snippets can be executed by modules like \pythonilIdx{doctest}~\cite{PSF2024DTIPE} or tools such as \pytest~\cite{KPDT2024HTRD}.
4242
Their output is the compared to the text following the snippet in the \pgls{docstring}.
4343
If the output matches this text, the test succeeds.

text/main/controlFlow/iteration/iteration.tex

+80-4
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
%
5858
\begin{sloppypar}%
5959
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}.
6161
This distinction is necessary because we want to allow some objects to be iterated over multiple times.%
6262
\end{sloppypar}%
6363
%
@@ -287,7 +287,7 @@
287287
A \pgls{doctest} is a unit test written directly into the \pgls{docstring} of a function or module. %
288288
We therefore insert small snippets of \python\ code and their expected output. %
289289
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}. %
291291
After the snippet, the expected output is written. %
292292
The \pglspl{doctest} can be by modules like \pythonilIdx{doctest}~\cite{PSF2024DTIPE} or tools such as \pytest~\cite{KPDT2024HTRD}~(\cref{ut:pytest}). %
293293
They collect the code, run it, and compare its output to the expected output in the \pgls{docstring}. %
@@ -356,7 +356,7 @@
356356
\end{sloppypar}%
357357
%
358358
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.
360360
We already created a beautiful program doing this efficiently in \cref{lst:loops:for_loop_sequence_primes}.
361361
This time, we will use set comprehension\pythonIdx{set!comprehension}\pythonIdx{comprehension!set}.
362362

@@ -591,7 +591,7 @@
591591
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.%
592592
%
593593
\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}.}%
595595
%
596596
\FloatBarrier%
597597
%
@@ -625,5 +625,81 @@
625625
\FloatBarrier%
626626
\endhsection%
627627
%
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+
%
628704
\endhsection%
629705
%

0 commit comments

Comments
 (0)