Event Coordinator - Help understanding the generator loop

Hello,
I’m working on the Event Coordinator project in the Intermediate Python section, and I’ve gotten some code to work properly, but I’m having trouble understanding what is happening on a step by step basis. Here is a link to the exercise, and I’ve added a slew of print statements to my code as follows:

guests = {}
def read_guestlist(file_name):
  text_file = open(file_name,'r')
  print('opening file')
  temp = None
  print(f'temp variable is currently set to: {temp}')
  while True:
    if temp is None:
      print(f'temp was None')
      line_data = text_file.readline().strip().split(",")
      print(f'line_data is:{line_data}')
    else:
      print(f'temp was set to: {temp}')
      line_data = temp.strip().split(",")
      print(f'line_data is: {line_data}')
    if len(line_data) < 2:
        print(f'Closing file')
        text_file.close()
        break
    print(f'Setting name variable')
    name = line_data[0]
    age = int(line_data[1])
    guests[name] = age
    temp = yield name

guest_list = read_guestlist('guest_list.txt')
for i in range(10):
  print(next(guest_list))

print(guest_list.send("Jane,35"))

The output is correct, but I’m having trouble understanding how the very last loop works when I use the send() method. Somehow the temp variable is being set as ‘Jane,35’, which again is exactly what I want, but I cannot understand how that variable gets that value. It must be something to do with the order of the looping that I am misunderstanding, but I can’t figure it out. Any help is appreciated. Thanks.

Something to keep in mind about yield, it is not a return and only exits the function, leaving all running variables intact. When we reenter the function, flow begins where it left off.

Right. So in this case is it correct to assume that after temp= yield name is executed that the function would start over from the top again? If that is the case I still don’t understand when exactly the temp variable is assigned to the data from the send("Jane,35") call at the bottom of the script.

The send() method sends the string as an argument, and it’s clear from the print statements that the else clause is being executed. For that to happen the temp variable has to already have been assigned a value, because it can’t be None or else the if clause would be executed.

The print statements also show that the temp variable is set to ‘Jane,35’, so somehow the temp variable is receiving the value 'Jane,35' before that else clause is executed.

Ok from what I can see, the value from the send() call gets sent directly to the yield, so in that case the temp variable is being set directly by the send() method call.

I added a couple more print statements that seem to clarify what is happening for me a little bit.

guests = {}
def read_guestlist(file_name):
  text_file = open(file_name,'r')
  print('opening file')
  temp = None
  print(f'temp variable is currently set to: {temp}')
  while True:
    if temp is None:
      print(f'temp was None')
      line_data = text_file.readline().strip().split(",")
      print(f'line_data is:{line_data}')
    else:
      print(f'temp was set to: {temp}')
      line_data = temp.strip().split(",")
      print(f'line_data is: {line_data}')
    if len(line_data) < 2:
        print(f'Closing file')
        text_file.close()
        break
    print(f'Setting name variable')
    name = line_data[0]
    age = int(line_data[1])
    guests[name] = age
    print(f'temp state 1: {temp}')
    temp = yield name
    print(f'temp state 2: {temp}')

guest_list = read_guestlist('guest_list.txt')
for i in range(10):
  print(next(guest_list))

print(guest_list.send("Jane,35"))

It seems that on a normal iteration the temp = yield name is never actually storing the value of the name but sort of instantly passing the name through and then it instantly reverts back to None. However, when we use the send() method it seems to send the value to the yield expression, which in this case is being stored in the temp variable, and therefore when the loop runs again we already have a value stored in temp which executes our else clause and then the rest of the function proceeds as it would have.

Confusing stuff, but I think I understand it now. Thanks for your help.

2 Likes

That’s how I understand it. Flow continues from the point of the last yield, and the loop repeats.

Don’t feel alone with this confusion, you’re not. Thank you for diligently testing out your theories.

2 Likes

Seems like you’ve already grasped the idea but I think it’s mentioned in the lessons or, if not, in the docs somewhere that you can equate a call to next with .send(None) when you’re using yield as an expression. So it’s an expression that always returns something, but unless you’re using .send it defaults to returning the None object.

As far as CPython goes I think yield is an expression, for the super simple generator below despite the fact it’s used as a statement it necessitates a POP_TOP operation (popping the expression return off the top of the stack).

def func():
    yield 3

  2           0 LOAD_CONST               1 (3)
              2 YIELD_VALUE
              4 POP_TOP
              ...

It just gets some special syntax rules to allow it to be used in statement form: https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement

2 Likes

Ah I didn’t realize that’s what is happening. Thank you for that. That makes sense in the context of the looping now. Temp was always reading as None because it was sending None every time, and the only time the temp variable was holding the value of the name is when it was being yielded and printed.

The lessons do sort of explain that, but it wasn’t clicking until I went through all of this manually. Sort of helps to think of it as like a return statement but capable of input/output from outside the function.

1 Like

Ain’t that the truth :grin:. Playing around with these things was the only thing that made sense to me. I’ve been familiar with generators, the simple form at least where .send is never used for quite a while. Using .send though made it quite a bit more complex and it only started clicking once I started poking around.