Python3 - Yield keyword
- take a text file that you may have.
and you want to read each line from the file:
def fetch_lines(filename):
with open(filename, "r") as file:
lines = []
for line in lines:
lines.append(line)
return lines
zen = fetch_lines("<file-name-here>.txt")
print(zen)
since we are storing the lines of the text file in list = []
, we are storing those lines in the memory of our program (stack/heap)
but what if the file was million's of lines long?
then our program would crash.
What if we just get one line at a time?
pull a line, do what we need to do, erase it from memory (the stack), and then move onto the next one.
That's where:
you can change the code above to instead use the yield
keyword.
def fetch_lines(filename):
with open(filename, "r") as file:
# lines = []
for line in lines:
yield line
# lines.append(line)
# return lines
zen = fetch_lines("<file-name-here>.txt")
print(zen)
instead of printing out the whole array, it will print out
<generator object fetch_lines at 0x00000002789FDSE>
to get values out of this generator object, we say next(zen)
and we will get the next line.
- @ It's like a for-loop that is frozen in time, and we can control the actions of that frozen for-loop dynamically.
Now that we know what Yield does...How can you use yield from
?
- example
def generator():
yield 1
yield 2
yield "hello world"
gen = generator()
next(gen)
next(gen)
next(gen)
you can pass an iterable or another generator to yield from
to get the same type of results or effect
like so:
def generator():
yield 1
yield 2
yield 3
def other_generator():
yield "goodbye"
yield from generator()
yield "hello again"
for item in other_generator():
print(item)
This will print out:
goodbye
1
2
3
"hello again"
This is a good paradigm to use when you are handling an iterable where you don't know the end length and don't want your program's memory to expand rapidly if you get a very large sized iterable (think millions).
-
! You can even send information into subgenerators in order to do further data handling or exception Handling
-
This is sometimes called "coroutine behavior"
once yield
ends up on the value side of the assignment, it turns the generator into a coroutine and allows the generator to receive values (like done by delegator
)
but when yield
is on the left side; it sends the value out of the generator.
def subgen():
while True:
x = yield
yield x * 2
def delegator():
yield from subgen()
g = delegator()
next(g) # prime the generator
g.send(3)
print(next(g)) # sends 3 to subgen, recieves 6
g.send(5)
print(next(g)) # sends 5 to subgen, recieves 10
if you need a more verbose example:
def verbose_subgen():
while True:
print("Waiting to receive...")
x = yield
print(f"Received: {x}")
result = x * 2
print(f"Sending back: {result}")
yield result
g = verbose_subgen()
next(g) # Prime the generator
print("Main: Sending 3")
g.send(3)
print(f"Main: Got back {next(g)}")
print("Main: Sending 5")
g.send(5)
print(f"Main: Got back {next(g)}")
You need to "prime" the generator in order for verbose_subgen
to get to it's first yield statement.
if you don't you get this error.
Traceback (most recent call last):
File "/Users/diegoguisande/Desktop/PARA/Projects_1/fastAPI/./scratch.py", line 33, in <module>
g.send(3)
TypeError: can't send non-None value to a just-started generator