for x in y(): how does this work?

PythonGeneratorYield

Python Problem Overview


I was looking for code to spin a cursor in the terminal and found this. I was wondering what was happening in the code. In particular for c in spinning_cursor(): I've never seen this syntax. Is it because I am returning one element from a generator at a time with yield, and this is assigned to c? Any other examples of this for x in y() use?

import sys
import time

def spinning_cursor():
    cursor='/-\|'
    i = 0
    while 1:
        yield cursor[i]
        i = (i + 1) % len(cursor)

for c in spinning_cursor():
    sys.stdout.write(c)
    sys.stdout.flush()
    time.sleep(0.1)
    sys.stdout.write('\b')

Python Solutions


Solution 1 - Python

Using yield turns a function into a generator. A generator is a specialized type of iterator. for always loops over iterables, taking each element in turn and assigning it to the name(s) you listed.

spinning_cursor() returns a generator, the code inside spinning_cursor() doesn't actually run until you start iterating over the generator. Iterating over a generator means the code in the function is executed until it comes across a yield statement, at which point the result of the expression there is returned as the next value and execution is paused again.

The for loop does just that, it'll call the equivalent of next() on the generator, until the generator signals it is done by raising StopIteration (which happens when the function returns). Each return value of next() is assigned, in turn, to c.

You can see this by creating the generator on in the Python prompt:

>>> def spinning_cursor():
...     cursor='/-\|'
...     i = 0
...     while 1:
...         yield cursor[i]
...         i = (i + 1) % len(cursor)
... 
>>> sc = spinning_cursor()
>>> sc
<generator object spinning_cursor at 0x107a55eb0>
>>> next(sc)
'/'
>>> next(sc)
'-'
>>> next(sc)
'\\'
>>> next(sc)
'|'

This specific generator never returns, so StopIteration is never raised and the for loop will go on forever unless you kill the script.

A far more boring (but more efficient) alternative would be to use itertools.cycle():

from itertools import cycle

spinning_cursor = cycle('/-\|')

Solution 2 - Python

In Python, the for statement lets you iterate over elements.

According the documentation :

> Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence

Here, the element will be the return value of spinning_cursor().

Solution 3 - Python

The for c in spinning_cursor() syntax is a for-each loop. It's going to iterate through each item in the iterator returned by spinning_cursor().

The inside of the loop will:

  1. Write the character to standard out and flush so it displays.
  2. Sleep for a tenth of a second
  3. Write \b, which is interpreted as a backspace (deletes the last character). Notice this happens at the end of the loop so it won't be written during the first iteration, and that it shares the flush call in step 1.

spinning_cursor() is going to return a generator, which doesn't actually run until you start iterating. It looks like it will loop through '/-\|', in order, forever. It's kind of like having an infinite list to iterate through.

So, the final output is going to be an ASCII spinner. You'll see these characters (in the same spot) repeating until you kill the script.

/
-
\
|

Solution 4 - Python

the spinning_cursor function returns an iterable (a generator from yield).

for c in spinning_cursor():

would be the same as

 for i in [1, 2, 3, 4]:

Solution 5 - Python

Martijn Pieters explanation is excellent. Below is another implementation of the same code you had in the question. it uses itertools.cycle to produce the same result as spinning_cursor. itertools is filled with excellent examples of iterators and functions to help create your own iterators. It might help you understand iterators better.

import sys, time, itertools

for c in itertools.cycle('/-\|'):
    sys.stdout.write(c)
    sys.stdout.flush()
    time.sleep(0.1)
    sys.stdout.write('\b')

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPaulView Question on Stackoverflow
Solution 1 - PythonMartijn PietersView Answer on Stackoverflow
Solution 2 - PythonaymericbeaumetView Answer on Stackoverflow
Solution 3 - PythonthegrinnerView Answer on Stackoverflow
Solution 4 - PythonbeillerView Answer on Stackoverflow
Solution 5 - PythonMarwan AlsabbaghView Answer on Stackoverflow