Python Yields are Fun!
While you can optimize the heck out of your Python code with
generators
and generator expressions
I'm more interested in goofing
around and solving classic programming questions with the yield
statement.
note: For this article, since it's easier to explain things as they happen, I'll be including a lot of inline comments.
Let's start with a simple function that returns a sequence of some of my favorite values:
# yielding.py
def pydanny_selected_numbers():
# If you multiple 9 by any other number you can easily play with
# numbers to get back to 9.
# Ex: 2 * 9 = 18. 1 + 8 = 9
# Ex: 15 * 9 = 135. 1 + 3 + 5 = 9
# See https://en.wikipedia.org/wiki/Digital_root
yield 9
# A pretty prime.
yield 31
# What's 6 * 7?
yield 42
# The string representation of my first date with Audrey Roy
yield "2010/02/20"
note: When a function uses the yield
keyword it's now called a
generator.
Let's do a test drive in the REPL:
>>> from yielding import pydanny_selected_numbers # import ye aulde code
>>> pydanny_selected_numbers() # create the iterator object
<generator object pydanny_selected_numbers at 0x1038a03c0>
>>> for i in pydanny_selected_numbers(): # iterate through the iterator
... print(i)
...
9
31
42
"2010/02/20"
>>> iterator = pydanny_selected_numbers() # create the iterator object
>>> for i in iterator: # iterate through the iterator object
... print(i)
...
9
31
42
"2010/02/20"
Of course, if you know anything about generator expressions, you know I could do this more tersely with the following:
>>> iterator = (x for x in [9, 31, 42, "2010/02/20"])
>>> for i in iterator:
... print(i)
...
9
31
42
"2010/02/20"
While that is more terse, it doesn't give us the amount of control we get by defining our own generator function. For example, what if I want to present the Fibonacci sequence in a loop rather than with recursion?
# fun.py
def fibonacci(max):
result = 0
base = 1
while result <= max:
# This yield statement is where the execution leaves the function.
yield result
# This is where the execution comes back into the function. This is
# just whitespace, but that it came back while preserving the state
# of the function is pretty awesome.
# Fibonacci code to increase the number according to
# https://en.wikipedia.org/wiki/Fibonacci_number
n = result + base
result = base
base = n
if __name__ == "__main__":
for x in fibonacci(144):
print(x)
Let's try this out in the REPL:
>>> from fun import fibonacci
>>> fibonacci(10)
<generator object fibonacci at 0x10d49e460>
>>> for x in fibonacci(10):
... print(x)
0
1
1
2
3
5
8
>>> iterator = fibonacci(5)
>>> iterator
<generator object fibonacci at 0x10d63c550>
>>> iterator.next()
0
>>> iterator.next()
1
What's nice about this is so much more than fibonacci logic in a
generator function. Instead, imagine instead of a lightweight
calculation I had done something performance intensive. By using
generator expressions I can readily control the execution calls with the
iterator object's next()
method, saving processor cycles.
Very nifty.
Summary
I admit it. Like many Python developers, I find using tools like yields and generators to optimize the heck out of performance intensive things a lot of fun.
If you are like me and like this sort of stuff, I recommend the following resources:
- Matt Harrison's Treading on Python Volume 2: Intermediate Python
- Jeff Knupp's Improve Your Python: 'yield' and Generators Explained
In the next article I'll demonstrate how to use the yield
statement
to create context managers.
Update: Nicholas Tollervey pointed me at wikipedia's Digital root article, so I added it to the comments of the first code sample.
Update: Oddthinking pointed out that I forgot a print statement. In the REPL it's not really needed, but if this is translated to a script then it's necessary.
Tags: python