Search code examples
pythongeneratoryield

Misunderstood python yield


This code below works correct :

def file_gen(f_name):
    f = open(f_name)
    for line in f:
        yield line

gen_line = file_gen("foo.html")
gen_line.next() # '<!DOCTYPE>\n'
gen_line.next() # '<html> \n' 
gen_line.next() # ... next line in file 

But this function raises StopIteration. I don't understand why ?

def file_gen(f_name):
    f = open(f_name)
    line = f.readline()
    yield line

gen_line = file_gen('foo.html')
gen_line.next()  # '<!DOCTYPE>\n'
gen_line.next()  # StopIteration

Solution

  • You have:

    def file_gen(f_name):
        f = open(f_name)
        line = f.readline()
        yield line
    

    Notice line = f.readline() This only reads 1 line from the file.

    Compare:

    def g(x):
        li=range(x)
        yield li.pop()
    
    print list(g(10))
    # [9]
    

    with this:

    def g(x):
        li=range(x)
        while li:
           yield li.pop()
    
    print list(g(10))
    # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
    

    yield can only be called once with a particular object or expression. Once it is used by the receiver it must be regenerated. So you need a loop around reading each line of the file.

    You can use your second (less readable) form this way:

    def file_gen(f_name):
        f = open(f_name)
        while True:
            line = f.readline()
            if not line:
                break
            yield line
    

    You need a loop to create the the items to yield. In your first case, for line in f: yield line is a loop.

    I would rewrite your function this way:

    def file_gen(f_name):
        with open(f_name) as f:
            for line in f:
                yield line