|
847 | 847 | \hsection{Generator Functions}% |
848 | 848 | \FloatBarrier% |
849 | 849 | % |
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}{% |
852 | 852 | A very simple generator function yielding the numbers~1, 2, and~3\pythonIdx{Generator}.}% |
853 | 853 | % |
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}{% |
856 | 866 | A generator function yielding the infinite sequence of Fibonacci numbers\pythonIdx{Generator}~\cite{W2024MAWWR:FN,S2022FLAATIMEOLPBOC}.}{}% |
857 | 867 | % |
858 | 868 | \gitLoadPython{iteration:prime_generator}{}{iteration/prime_generator.py}{}% |
859 | 869 | \listingPython{iteration:prime_generator}{% |
860 | 870 | A generator function yielding the infinite sequence of prime numbers\pythonIdx{Generator}~\cite{W2024MAWWR:PN,CP2005PNACP,R1994PNACMFF}.}% |
861 | 871 | % |
| 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 | +% |
862 | 877 | 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. |
864 | 879 | We can process the sequence of values provided by this \pythonilIdx{Iterator} in exactly the same ways already discussed. |
865 | 880 | We can iterate over it using a \pythonilIdx{for}~loop. |
866 | 881 | We can use it a comprehension or pass it to the constructor of a collection, if we want to. |
867 | 882 |
|
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. |
869 | 884 | Instead of using the \pythonilIdx{return} keyword, this is achieved by using the \pythonilIdx{yield} keyword. |
870 | 885 | 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. |
872 | 887 |
|
873 | 888 | 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. |
875 | 890 | 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. |
877 | 892 | The function body consists only of the three statements \pythonil{yield 1}, \pythonil{yield 2}, and \pythonil{yield 3}\pythonIdx{yield}. |
878 | 893 |
|
879 | 894 | We can use the \pythonilIdx{Generator} returned by this function to populate a \pythonilIdx{list}: |
880 | 895 | \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()}. |
883 | 898 | The first time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{1}. |
884 | 899 | The second time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{2}. |
885 | 900 | The third time we invoke \pythonil{next(gen)}\pythonIdx{next}, it returns~\pythonil{3}. |
886 | 901 | The fourth call to \pythonil{next(gen)}\pythonIdx{next} raises\pythonIdx{raise} a \pythonilIdx{StopIteration}. |
887 | 902 | This indicates that the end of the sequence is reached. |
888 | 903 | Indeed, we queried the generator function's result exactly like a normal~\pythonilIdx{Iterator}. |
889 | 904 |
|
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. |
891 | 909 | This becomes visible when we create a generator function that returns an infinite sequence. |
892 | 910 |
|
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}. |
896 | 917 | 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. |
899 | 925 | 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. |
902 | 928 |
|
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}. |
906 | 933 |
|
907 | 934 | It should be mentioned that doing something like \pythonil{list(fibonacci())} would be a very bad idea. |
908 | 935 | It would attempt to produce an infinitely large list, which would lead to an \pythonilIdx{MemoryError}. |
909 | 936 |
|
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. |
913 | 944 | 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. |
915 | 946 | 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}. |
917 | 969 |
|
918 | | -We demonstrate how our generator works with a \pgls{doctest}. |
| 970 | +We demonstrate how our generator function works with a \pgls{doctest}. |
919 | 971 | The test begins by instantiating the \pythonilIdx{Generator} as \pythonil{gen = primes()}. |
920 | 972 | The first \pythonil{next(gen)}\pythonIdx{next} call is supposed to return~\pythonil{2}. |
921 | 973 | The second such call shall return~\pythonil{3}, the third one~\pythonil{5}, the fourth one~\pythonil{7}. |
922 | 974 | The fifth and last \pythonil{next(gen)}\pythonIdx{next} invocation in the \pgls{doctest} should return~\pythonil{11}. |
923 | 975 | 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.% |
924 | 989 | \FloatBarrier% |
925 | 990 | \endhsection% |
926 | 991 | % |
|
0 commit comments