Let’s suppose we have a container object (such as a tuple) whose elements may include other containers, and those containers may themselves contain further containers. The goal is to create a list that collects all the elements found within the original container, regardless of how deeply nested they are.
By „elements”, we mean anything that is not a container object—including objects of type str and bytes, which are treated as atomic values even though they are technically iterable.
This process is commonly referred to as flattening. Conceptually, you can consider the container as the base level, and each nested container adds an additional level of depth. Each level contains one or more items, and our task is to collect all these items and place them on a single level.
This idea can be illustrated as follows:

There are multiple ways to address this problem. In this post, we’ll demonstrate a relatively simple solution based on a recursive generator function.
The core idea of the solution is as follows:
- Iterate over the elements of the given container and check whether the current element is iterable.
- If it is not iterable, then we’ve found a target element, and we yield it back to the caller.
- If it is iterable, we apply the same process (Step 1) to each of its elements.
We do this conveniently by using a yield from statement, where we recursively call the same generator function, passing the current item (which is itself an iterable) as the argument.
Here’s how this logic can be implemented in code:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from typing import Iterable def extract_elements(iterable_obj: Iterable): for item in iterable_obj: try: # Check if the item is iterable. iter(item) # If it is, no exception is raised, so we recursively # call this function to extract the nested elements. # Since str and bytes objects are treated as atomic values # (even though they are iterable), we do not recurse into them. if isinstance(item, (str, bytes)): yield item else: yield from extract_elements(item) # If the item is not iterable, yield it as is. except TypeError: yield item container = (0, [[1, {2}]], '345', ((6, '78'), b'9'), range(10, 13)) # TEST print(list(extract_elements(container))) # Output: # [0, 1, 2, '345', 6, '78', b'9', 10, 11, 12] |
Although the code may seem simple, understanding how it works requires a fair amount of prior knowledge. Fortunately, all the necessary concepts are covered in detail in the e-book Python Knowledge Building Step by Step.
It’s worth noting that recursion should always be used with care, as very deep levels of nesting can cause the program to crash due to a recursion depth limit. In our case, however, this is not a concern in practice, since the number of nesting levels — which determines the recursion depth — is typically quite low.