Python3 - Yield keyword


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:

Python Generators come in...

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.


Now that we know what Yield does...How can you use yield from?


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).

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