
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/generators","feed_url":"https:\/\/mathspp.com\/blog\/tags\/generators.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"Binary search as a bidirectional generator","date_published":"2025-03-03T14:20:00+01:00","id":"https:\/\/mathspp.com\/blog\/binary-search-as-a-bidirectional-generator","url":"https:\/\/mathspp.com\/blog\/binary-search-as-a-bidirectional-generator","content_html":"<p>This article proposes an implementation of an ergonomic binary search algorithm implemented as a bidirectional generator.<\/p>\n\n<p>Python generators can be bidirectional and in this article I will use this feature to try to implement the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Binary_search\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">binary search<\/a> algorithm in an ergonomic way.\nTo this end, I'll write a bidirectional generator that implements the binary search algorithm independent of any use cases.\nThis means the resulting generator can be used ergonomically whenever a binary search needs to be employed.<\/p>\n<h2 id=\"bidirectional-generators\">Bidirectional generators<a href=\"#bidirectional-generators\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Python generators support the method <code>send<\/code> which allows sending data into a generator.\nTo receive data inside a generator, you can assign a <code>yield<\/code> statement to a variable and when the user calls the method <code>send<\/code>, the argument passed in will be stored in the variable used.\nIt's also important to note that the method <code>send<\/code> will send data into the generator and it will also execute the generator up to its next <code>yield<\/code> statement.<\/p>\n<p>The snippet below shows how the method <code>send<\/code> interacts with a generator:<\/p>\n<pre><code class=\"language-py\">def my_gen():\n    yield 1\n    yield 2\n    value = yield 3\n    print(value)\n    yield 5\n    yield 6\n\nvalues = my_gen()\nprint(next(values))  # 1\nprint(next(values))  # 2\nprint(next(values))  # 3\nvalues.send(4)  # 4\nprint(next(values))  # 6<\/code><\/pre>\n<p>Note that the <code>5<\/code> was skipped because it was yielded when you sent the <code>4<\/code> into the generator and we didn't catch it outside the generator.<\/p>\n<p>Because of the way the method <code>send<\/code> interacts with generators, a generator that has been instantiated but hasn't been started yet can't be sent any data but it can be started with a call to <code>send(None)<\/code>, which will run the generator up to its first <code>yield<\/code>.<\/p>\n<h2 id=\"binary-search\">Binary search<a href=\"#binary-search\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you abstract the algorithm away from its concrete applications, the binary search algorithm produces a sequence of integer guesses that are candidates to satisfy some criteria.\nSince binary search produces these values consecutively, and it doesn't need to generate every possible value ahead of time, it makes sense to consider a generator as the tool to implement binary search.<\/p>\n<p>Additionally, depending on the result of an evaluation that is performed on a guess produced by the algorithm, the next guess might be above or below the previous guess.\nThis is why the generator needs to be bidirectional, since the user will inform the generator on whether guesses are currently too high or too low.<\/p>\n<p>To inform the binary search generator of whether the guesses need to be increased or decreased, we will send the following integers to the binary search:<\/p>\n<ul><li><code>-1<\/code>: the guess is too high;<\/li>\n<li><code>0<\/code>: the guess is correct; and<\/li>\n<li><code>1<\/code>: the guess is too low.<\/li>\n<\/ul><p>With this in mind, here is an example usage of the generator I am proposing:<\/p>\n<pre><code class=\"language-py\">import random\n\nsn = random.randint(0, 100)  # Secret number to be guessed.\n\nsearcher = binary_search(0, 100)\nfor guess in searcher:\n    feedback = 1 if guess &lt; sn else -1 if guess &gt; sn else 0\n    searcher.send(feedback)\n\nprint(f\"Secret number was {guess}.\")\n# Secret number was 42.<\/code><\/pre>\n<p>The...<\/p>","summary":"This article proposes an implementation of an ergonomic binary search algorithm implemented as a bidirectional generator.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","generators","programming","python"],"image":"\/user\/pages\/02.blog\/binary-search-as-a-bidirectional-generator\/thumbnail.webp"},{"title":"itertools.batched","date_published":"2023-10-17T00:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/itertools-batched","url":"https:\/\/mathspp.com\/blog\/itertools-batched","content_html":"<p>Learn how <code>batched<\/code> from the module <code>itertools<\/code> works, example use cases, and how to implement it.<\/p>\n\n<p>The module <code>itertools<\/code> introduced a new tool called <code>batched<\/code> in Python 3.12.\n<code>itertools.batched<\/code> lets you iterate over an iterable by going over portions of that iterable &ndash; or batches &ndash; that all have the same size, except possibly for the last one.\nSome <a href=\"#example-use-cases\">example use cases<\/a> include batching API requests or batching data processing.<\/p>\n<p>As a dummy example, consider the snippet below:<\/p>\n<pre><code class=\"language-py\">&gt;&gt;&gt; from itertools import batched\n&gt;&gt;&gt; for batch in batched(\"Hello, world!\", 3):\n...     print(batch)\n...\n('H', 'e', 'l')\n('l', 'o', ',')\n(' ', 'w', 'o')\n('r', 'l', 'd')\n('!',)<\/code><\/pre>\n<p>Notice how the last batch is a tuple with the single character, <code>\"!\"<\/code>.\nThat's because the original input string had length 13 and I asked for batches of size 3, so <code>batched<\/code> served me with four batches of size 3 and the final batch contained the remaining character.<\/p>\n<h2 id=\"example-use-cases\">Example use cases<a href=\"#example-use-cases\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>In this section I will show you a couple of example use cases for <code>itertools.batched<\/code>.\nIf you know of any other good use cases, feel free to leave a comment below or to <a href=\"\/contact-me\">reach out to me<\/a> and I'll include them here.<\/p>\n<h3 id=\"go-over-a-file-in-chunks\">Go over a file in chunks<a href=\"#go-over-a-file-in-chunks\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>If you have a huge file that you need to go over, but you don't need to read the whole file at once, you can use <code>batched<\/code> to go over a set number of lines at a time.\nThe code would look like this:<\/p>\n<pre><code class=\"language-py\">from itertools import batched\n\nwith open(some_file, \"r\") as file:\n    for chunk in batched(file, 25):\n        process_chunk_of_lines(chunk)<\/code><\/pre>\n<p>This could work well if you were looking for some specific line in the file, for example, and you couldn't hold the whole file in memory.\nOf course, in that case, you could probably increase the batch size to something bigger than <code>25<\/code>.<\/p>\n<h3 id=\"chunking-a-response-over-a-socket\">Chunking a response over a socket<a href=\"#chunking-a-response-over-a-socket\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>In contexts like socket communications, it is common to have to chunk your response to a maximum size, so if your message is bigger than some limit, you have to send it in chunks.\nThe code would look something like this:<\/p>\n<pre><code class=\"language-py\">from itertools import batched\n\nfor chunk in batched(raw_data, 1024):\n    sent = socket.send(b\"\".join(chunk))\n    if sent &lt; len(chunk):\n        # Handle the fact that not all data was sent.<\/code><\/pre>\n<p>In this case, you may need to send strings (or bytes) over the socket, and that is why we do <code>b\"\".join(...)<\/code>, because <code>batched<\/code> returns tuples with the elements.<\/p>\n<h3 id=\"iterating-over-substrings\">Iterating over substrings<a href=\"#iterating-over-substrings\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>If you need to use <code>batched<\/code> to split a string, but the final thing you need is substrings, you can use <code>batched<\/code> together with <code>\"\".join<\/code> and <code>map<\/code> to create an iterator that produces substrings of a given length:<\/p>\n<pre><code class=\"language-py\">map(\"\".join, batched(string, length))<\/code><\/pre>\n<p>Here is an example:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; from itertools import batched\n&gt;&gt;&gt; hello_world_substrings = map(\"\".join, batched(\"Hello, world!\", 3))\n&gt;&gt;&gt; for substring in hello_world_substrings:\n...     print(substring)\n...\nHel\nlo,\n wo\nrld\n!<\/code><\/pre>\n<p>This could also work well with the <a href=\"#chunking-a-response-over-a-socket\">socket example<\/a> from above.<\/p>\n<p>Another example where...<\/p>","summary":"Learn how batched from the module itertools works, example use cases, and how to implement it.","date_modified":"2025-07-23T16:49:02+02:00","tags":["dunder methods","generators","modules","programming","python"],"image":"\/user\/pages\/02.blog\/itertools-batched\/thumbnail.webp"},{"title":"TIL #071 \u2013 generator method close","date_published":"2023-07-21T23:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/generator-method-close","url":"https:\/\/mathspp.com\/blog\/til\/generator-method-close","content_html":"<p>Today I learned about the generator method <code>close<\/code>.<\/p>\n\n<h2 id=\"the-generator-method-close\">The generator method <code>close<\/code><a href=\"#the-generator-method-close\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Python generators have a method called <code>close<\/code> that \u201ccloses\u201d the generator.\nClosing the generator means that if you call <code>next<\/code> on it again, it will raise <code>StopIteration<\/code>, which is essentially the same as if the generator had been exhausted completely.<\/p>\n<p>Here is a trivial example that shows this method in use:<\/p>\n<pre><code class=\"language-py\">&gt;&gt;&gt; squares = (num ** 2 for num in range(10))\n&gt;&gt;&gt; for square in squares:\n...     print(square)\n...     if square &gt; 20:\n...         squares.close()\n...\n0\n1\n4\n16\n25<\/code><\/pre>\n<p>I learned this from <a href=\"https:\/\/ep2023.europython.eu\/session\/what-are-you-yield-from\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">a poster session I attended at EuroPython 2023<\/a> by Maxim Danilov.<\/p>\n<h2 id=\"an-infinite-loop-for-that-is-cancellable\">An infinite loop <code>for<\/code> that is cancellable<a href=\"#an-infinite-loop-for-that-is-cancellable\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>In his poster session, Maxim proposed a really interesting pattern with an infinite loop <code>for<\/code> that could easily get \u201ccancelled\u201d\u00a0by calling the method <code>close<\/code> on the infinite generator.\nMaxim said there were examples in the standard library where this example could be interesting, but sadly I don't remember exactly where he said we could do it.<\/p>\n<p>Essentially, the pattern uses the infinite iterator <code>iter(int, 1)<\/code> to bootstrap a generator and then it uses the method <code>close<\/code>:<\/p>\n<pre><code class=\"language-py\">import random\n\ninfinity = (_ for _ in iter(int, 1))\nfor _ in infinity:\n    print(\"v\")\n    if random.random() &lt; 0.05:\n        infinity.close()\n        print(\"Loop `for` has been cancelled.\")<\/code><\/pre>\n<p>If you run this piece of code, you will get output like this:<\/p>\n<pre><code class=\"language-txt\">v\nv\nv\nv\nv\nv\nv\nLoop `for` has been cancelled.<\/code><\/pre>\n<p>If I understood correctly, Maxim claimed that there was a common pattern with some infinite loops implemented in terms of <code>while True:<\/code>.\nIn certain situations, those loops can get messy because of the logic used inside the loop to determine when to use the keyword <code>break<\/code>!<\/p>\n<p>This is relevant because calling <code>close<\/code> on the generator that we are iterating over will <em>prevent the next iteration<\/em> but it will finish running the code of the current iteration.\nOn the other hand, the keyword <code>break<\/code> will exit the loop altogether as soon as it is encountered, skipping the remainder of the code in the loop.<\/p>\n<p>This was a very specific use case for <code>close<\/code>, but <code>close<\/code> can be used with any generator; it doesn't have to be an infinite one.<\/p>\n<p>That's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>","summary":"Today I learned about the generator method `close`.","date_modified":"2025-07-23T16:49:02+02:00","tags":["generators","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/071.generator-method-close\/thumbnail.webp"},{"title":"TIL #069 \u2013 generator membership testing","date_published":"2023-07-17T22:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/generator-membership-testing","url":"https:\/\/mathspp.com\/blog\/til\/generator-membership-testing","content_html":"<p>Today I learned that generators support membership testing with the operator <code>in<\/code>.<\/p>\n\n<h2 id=\"generator-membership-testing\">Generator membership testing<a href=\"#generator-membership-testing\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Membership testing in Python boils down to using the operator <code>in<\/code>, as the REPL session excerpt shows:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; my_list = [42, 73, 16, 0, 10]\n&gt;&gt;&gt; 42 in my_list\nTrue\n&gt;&gt;&gt; 43 in my_list\nFalse\n&gt;&gt;&gt; 10 in my_list\nTrue<\/code><\/pre>\n<p>What I hadn't realised yet is that generators also support membership testing!\nIt is very easy to verify that they <em>do<\/em> support membership testing, though:<\/p>\n<pre><code class=\"language-py\">def generator():\n    yield 42\n    yield 73\n    yield 16\n    yield 0\n    yield 10\n\ngen = generator()\nprint(42 in gen)  # True<\/code><\/pre>\n<h2 id=\"gotchas-of-generator-membership-testing\">Gotchas of generator membership testing<a href=\"#gotchas-of-generator-membership-testing\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>What I immediately realised afterwards is that you need to be very careful about membership testing with generators.\nFor example, consider the generator function from before:<\/p>\n<pre><code class=\"language-py\">def generator():\n    yield 42\n    yield 73\n    yield 16\n    yield 0\n    yield 10<\/code><\/pre>\n<p>We can check if the number 999 is in there and we will get the correct answer:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; gen = generator()\n&gt;&gt;&gt; 999 in gen\nFalse<\/code><\/pre>\n<p>But now, think about it.\nHow can Python tell that 999 is not inside the generator <code>gen<\/code>?\nIt had to iterate over it, which means it had to go through all the <code>yield<\/code> expressions already!\nIn turn, this means that the generator was already exhausted.\nIt is already empty:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; next(gen)\nTraceback (most recent call last):\n  File \"&lt;stdin&gt;\", line 1, in &lt;module&gt;\nStopIteration<\/code><\/pre>\n<p>In practice, if a generator reports that a given value is <em>not<\/em> in the generator, then it is probably because of this issue; because you had already iterated <em>past<\/em> the value that you were looking for.<\/p>\n<p>Let me show you another example of this:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; def generator():\n...     yield 1\n...     yield 2\n...     yield 3\n...\n&gt;&gt;&gt; gen = generator()\n&gt;&gt;&gt; 999 in gen\nFalse\n&gt;&gt;&gt; 1 in gen\nFalse\n&gt;&gt;&gt; 2 in gen\nFalse\n&gt;&gt;&gt; 3 in gen\nFalse<\/code><\/pre>\n<p>This example might look obvious to you because I am using a non-existing value to exhaust the generator.\nHowever, this issue can also arise after successful membership tests:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; def generator():\n...     yield 1\n...     yield 2\n...     yield 3\n...\n&gt;&gt;&gt; gen = generator()\n&gt;&gt;&gt; 2 in gen\nTrue\n&gt;&gt;&gt; 1 in gen\nFalse<\/code><\/pre>\n<p>To make sure you understand what's going on, try to guess the output of the three membership checks below.\nOther than that small challenge, that's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; def generator():\n...     yield 1\n...     yield 2\n...     yield 3\n...\n&gt;&gt;&gt; gen = generator()\n&gt;&gt;&gt; 3 in gen\n## ???\n&gt;&gt;&gt; 1 in gen\n## ???\n&gt;&gt;&gt; 2 in gen\n## ???<\/code><\/pre>\n<p>That's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>","summary":"Today I learned that generators support membership testing with the operator `in`.","date_modified":"2025-07-23T16:49:02+02:00","tags":["generators","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/069.generator-membership-testing\/thumbnail.webp"},{"title":"TIL #058 \u2013 do not cache generators","date_published":"2023-04-12T10:30:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/do-not-cache-generators","url":"https:\/\/mathspp.com\/blog\/til\/do-not-cache-generators","content_html":"<p>Today I learned not to cache generators.<\/p>\n\n<h2 id=\"do-not-cache-generators\">Do not cache generators<a href=\"#do-not-cache-generators\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I was <a href=\"\/blog\/til\/optimising-images-for-the-web\">optimising all images on my blog<\/a> and I noticed my script was not working correctly.\nIt was skipping some images...<\/p>\n<p>That is when I realised I was caching some generators and I shouldn't!<\/p>\n<p>Here is a simplified demonstration of the issue:<\/p>\n<pre><code class=\"language-py\">from functools import lru_cache\n\n@lru_cache\ndef squares(n):\n    return (num ** 2 for num in range(n))\n\nfor square in squares(3):\n    print(square)\n\nfor square in squares(3):\n    print(square)<\/code><\/pre>\n<p>What's the output of this code?\nI expected it to be<\/p>\n<pre><code>0\n1\n4\n0\n1\n4<\/code><\/pre>\n<p>But it's actually<\/p>\n<pre><code>0\n1\n4<\/code><\/pre>\n<p>Why is that?\nBecause the <code>lru_cache<\/code> cached the generator and the generator is an <em>iterator<\/em>.\nAs soon as you go over it once, it is \"depleted\" and you can't iterate over it again!<\/p>\n<p>Here is another demonstration of the issue:<\/p>\n<pre><code class=\"language-py\">from functools import lru_cache\n\n@lru_cache\ndef squares(n):\n    return (num ** 2 for num in range(n))\n\nsquares_to_3 = squares(3)\nfor square in squares_to_3:\n    print(square)\n\nnew_squares_to_3 = squares(3)\nprint(new_squares_to_3 is squares_to_3)  # True, they are the SAME object.\n\n## Hence, \"new\" squares_to_3 has actually been traversed:\nprint(next(new_squares_to_3, None))  # None, there is no next element.<\/code><\/pre>\n<p>That's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>","summary":"Today I learned not to cache generators.","date_modified":"2025-07-23T16:49:02+02:00","tags":["generators","optimisation","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/058.do-not-cache-generators\/thumbnail.webp"},{"title":"TIL #010 \u2013 generator return","date_published":"2021-10-09T00:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/010","url":"https:\/\/mathspp.com\/blog\/til\/010","content_html":"<p>Today I learned that Python generators can return a value.<\/p>\n\n<script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script>\n<p><img alt=\"Code showing a generator that returns a value.\" src=\"\/images\/6\/9\/4\/1\/5\/694150be07a566d742288983df8f5bbd6e4db274-thumbnail.webp\"><\/p>\n<h2 id=\"generators\">Generators<a href=\"#generators\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Generators are interesting Python objects that produce a series of values,\nbut one by one.\nIn a way, they can be thought of as stateful functions.\n(That is, functions with state.)<\/p>\n<p>What I learned is that generators can also return something.\nHere is the tweet that prompted this discovery:<\/p>\n<blockquote class=\"twitter-tweet\">\n<p lang=\"en\" dir=\"ltr\"><a href=\"https:\/\/twitter.com\/hashtag\/Python?src=hash&amp;ref_src=twsrc%5Etfw\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">#Python<\/a> pop quiz: Is this code valid? If so, what does it do?<br><br>def f():<br> yield 10<br> return 20<br><br>g = f()<br>print(next(g))<br>print(next(g))<\/p>\u2014 Raymond Hettinger (@raymondh) <a href=\"https:\/\/twitter.com\/raymondh\/status\/1446191250470735878?ref_src=twsrc%5Etfw\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">October 7, 2021<\/a>\n<\/blockquote>\n<h2 id=\"returning-from-a-generator\">Returning from a generator<a href=\"#returning-from-a-generator\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>When you use a <code>return<\/code> inside a generator,\nthe generator will have that returned information in its <code>StopIteration<\/code>\nexception when it's done:<\/p>\n<pre><code class=\"language-py\">&gt;&gt;&gt; def f():\n...     yield 10\n...     return 20\n...\n&gt;&gt;&gt; gen = f()\n&gt;&gt;&gt; next(gen)\n10\n&gt;&gt;&gt; next(gen)\nTraceback (most recent call last):\n  File \"&lt;stdin&gt;\", line 1, in &lt;module&gt;\nStopIteration: 20<\/code><\/pre>\n<p>If you want to get access to that value, you just need to catch the exception:<\/p>\n<pre><code class=\"language-py\">&gt;&gt;&gt; gen = f(); next(gen);\n10\n&gt;&gt;&gt; try:\n...     next(gen)\n... except StopIteration as e:\n...     val = e.value\n...\n&gt;&gt;&gt; val\n20<\/code><\/pre>\n<p>That's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>","summary":"Today I learned that Python generators can return a value.","date_modified":"2025-07-23T16:49:02+02:00","tags":["generators","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/010.generator-return\/thumbnail.webp"}]}
