When I'm writing Python, I often think that the nesting is deep and difficult to read. Specifically, it is the following cases.
for group in groups:
for member in group.members:
for article in member.articles:
for tag in article.tags:
"""
Various processing
"""
Wouldn't it be nice if this looks like this?
for group, member, article, tag in func(groups):
"""
Various processing
"""
However, I think there is a question, "How can I control the for statement by myself?", So I would like to explain from there.
Some programming languages, including Python, have iterators and generators. For Python, the following articles will be helpful. Python Iterators and Generators
This time, we will implement it using this generator.
First, lightly check the operation.
def my_generator():
yield 10
yield 20
yield 30
for num in my_generator():
print(num)
"""Output result
10
20
30
"""
You can see that the values passed in yield are the arguments of the for statement one after another.
Based on this, after passing the sample groups, create a function that expands to tag and passes it to the argument of for.
def group_generator(groups):
for group in groups:
for member in group.members:
for article in member.articles:
for tag in article.tags:
yield group, member, article, tag
for group, member, article, tag in group_generator(groups):
print(group)
print(member)
print(article)
print(tag)
Now you have a shallow source code.
Let's generalize this further.
group_generator
In generalizing, after the second for statement, the process of extracting the next iterable object from a certain value and turning it with the for statement is common, and you can see that it is recursive. Also, it seems that it can be implemented well by using the yeild part as the base case of the recursive function.
** Processing to extract the next Iterable object from a certain value ** is iterable_creators
, ** Base object ** is `base_iterable```, ** Recursive part ** When implemented as a
nest_recursive``` function, it looks like this: If you want to call the generator function inside the function, use ``
yield from` ``.
def nests(base_iterable, *iterable_creators):
def nest_recursive(nested_items: Tuple, current_item, level: int):
try:
iterable_creator = iterable_creators[level]
next_iterable = iterable_creator(current_item)
for next_item in next_iterable:
new_stuck = (*nested_items, next_item)
yield from nest_recursive(new_stuck, next_item, level + 1)
except IndexError:
yield nested_items
for root_item in base_iterable:
yield from nest_recursive((root_item,), root_item, level=0)
Let's actually use it.
iterable_creators = (lambda group: group.members, lambda member: member.articles, lambda article: article.tags)
for group, member, article, tag in nests(groups, *iterable_creators):
print(group)
print(member)
print(article)
print(tag)
The nesting has been removed to make it easier to read. It is doubtful whether it is practical, but it was a good study for the generator.
Recommended Posts