{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/python/enable-repeatable-lazy-iterations/",
"description": "Create reusable generators by implementing __iter__ in a class, allowing multiple lazy iterations without memory overhead or repeated function calls.",
"path": "/python/enable-repeatable-lazy-iterations/",
"publishedAt": "2023-07-13T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"TIL",
"Python"
],
"textContent": "The current title of this post is probably incorrect and may even be misleading. I had a\nhard time coming up with a suitable name for it. But the idea goes like this: sometimes you\nmight find yourself in a situation where you need to iterate through a generator more than\nonce. Sure, you can use an iterable like a tuple or list to allow multiple iterations, but\nif the number of elements is large, that'll cause an OOM error. On the other hand, once\nyou've already consumed a generator, you'll need to restart it if you want to go through it\nagain. This behavior is common in pretty much every programming language that supports the\ngenerator construct.\n\nSo, in the case where a function returns a generator and you've already consumed its values,\nyou'll need to call the function again to generate a new instance of the generator that you\ncan use. Observe:\n\nThis can be used like this:\n\nIt'll return:\n\nNow, if you try to consume the iterable again, you'll get empty value. Run this again:\n\nIt won't print anything since the previous loop has exhausted the generator. This is\nexpected and if you want to loop through the same elements again, you'll have to call the\nfunction again to produce another generator that you can consume. So, the following will\nalways work:\n\nIf you run this snippet multiple times, on each pass, the get_numbers() function will be\ncalled again and that'll return a new generator for you to iterate through. Calling the\ngenerator function like this works but here's another thing that I learned today while\nreading [Effective Python] by Brett Slatkin. You can create a class with the __iter__\nmethod and yield numbers from it just like the function. Then when you initiate the class,\nthe instance of the class will allow you to loop through it multiple times; each time\ncreating a new generator.\n\n> I knew that you could create an iterable class by adding __iter__ to a class and\n> yielding values from it. But I wasn't aware that the you could also iterate through the\n> instance of the class multiple times and the class will run __iter__ on each pass and\n> produce a new generator for you to consume.\n\nFor example:\n\nNow use the class as such:\n\nThis prints:\n\nIf you run the for-loop again on the number instance, you'll see that the snippet will print\nthe same numbers again. Here, instantiating the NumberGen class creates a NumberGen\ninstance that is not a generator per se, but can return a generator if you call the iter()\nfunction on the instance. When you run the for loop on the instance, it runs the underlying\n__iter__ method to produce a new generator that the loop can iterate through. This allows\nyou to run the for-loop multiple times on the instance, since each run creates a new\ngenerator that the loop can consume.\n\n> A generator can still only be consumed once but each time you're running a new for-loop on\n> the above instance, the __iter__ method on it gets called and the method returns a new\n> generator for you to iterate through.\n\nThis is more convenient than having to repeatedly call a generator function if your API\nneeds to consume a generator multiple times.\n\n\n\n\n[effective python]:\n https://effectivepython.com/",
"title": "Enabling repeatable lazy iterations in Python"
}