Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Custom Iterator: Close a file on StopIteration

I have written an iterator class that opens a file in it's __init__.

def __init__(self, path):
    self.file = open(path, "r")

How do I close that file automatically when the iteration is finished?

Complete class:

class Parse(object):
    """A generator that iterates through a CC-CEDICT formatted file, returning
   a tuple of parsed results (Traditional, Simplified, Pinyin, English)"""
    def __init__(self, path):
        self.file = open(path, "r")

    def __iter__(self):
        return self

    def __is_comment(self, line):
        return line.startswith("#")

    def next(self):
        #This block ignores comments.
        line = self.file.readline()
        while line and self.__is_comment(line):
            line = self.file.readline()

        if line:
            working = line.rstrip().split(" ")
            trad, simp = working[0], working[1]
            working = " ".join(working[2:]).split("]")
            pinyin = working[0][1:]
            english = working[1][1:]
            return trad, simp, pinyin, english

        else:
            raise StopIteration()  
like image 863
jsj Avatar asked Feb 10 '13 12:02

jsj


People also ask

What is StopIteration in Python?

When the specified number of iterations are done, StopIteration is raised by the next method in case of iterators and generators (works similar to iterators except it generates a sequence of values at a time instead of a single value).

How to prevent iteration to go on forever in Python?

To prevent the iteration to go on forever, we can use the StopIteration statement. In the __next__ () method, we can add a terminating condition to raise an error if the iteration is done a specified number of times:

How do you use iterators in Python?

Python allows you to use iterators in for loops, comprehensions, and other built-in functions including map, filter, reduce, and zip. The following example defines Square iterator class that returns the square numbers. Note that a square number is a product of an integer with itself. How it works.

Do I need to close the Python interpreter files when exiting?

In Python 2.5 (using future) and in Python 2.6, you no longer need the wordy version: Upon exit the Python interpreter (or the kernel in the event of a crash) will close the file, but it's still a good practice to close them when you don't need them.


2 Answers

A better way to write the whole thing would be to keep the opening and the iteration in one place:

class Parse(object):
    """A generator that iterates through a CC-CEDICT formatted file, returning
    a tuple of parsed results (Traditional, Simplified, Pinyin, English)"""
    def __init__(self, path):
        self.path = path

    def __is_comment(self, line):
        return line.startswith("#")

    def __iter__(self):
        with open(self.path) as f:
            for line in f:
                if self.__is_comment(line):
                    continue

                working = line.rstrip().split(" ")
                trad, simp = working[0], working[1]
                working = " ".join(working[2:]).split("]")
                pinyin = working[0][1:]
                english = working[1][1:]
                yield trad, simp, pinyin, english

This will wait to open the file until you really need it, and will automatically close it when done. It's also less code.

If you really want to get into the "generators are awesome!" mindset:

def skip_comments(f):
    for line in f:
        if not.startswith('#'):
            yield line

...

    def __iter__(self):
        with open(self.path) as f:
            for line in skip_comments(f):
                working = ....
like image 121
Ned Batchelder Avatar answered Sep 23 '22 19:09

Ned Batchelder


You need to explicitly close it as soon as StopIteration is raised. In this case, simply call .close() when you raise StopIteration yourself.

def next(self):
    #This block ignores comments.
    line = self.file.readline()
    while line and self.__is_comment(line):
        line = self.file.readline()

    if line:
        working = line.rstrip().split(" ")
        trad, simp = working[0], working[1]
        working = " ".join(working[2:]).split("]")
        pinyin = working[0][1:]
        english = working[1][1:]
        return trad, simp, pinyin, english

    else:
        self.file.close()
        raise StopIteration()  

Since no other code in your .next() method could trigger a StopIteration this suffices.

If you did use next() on another iterator inside your own .next() you'd have to catch StopIteration with an except StopIteration: handler and reraise the exception.

This only handles the StopIteration case. If you want to handle other situations (not exhausting the iterator) you'll need to handle that situation separately. Making your class a Context Manager as well could help with that. Users of your iterator would then use the object in a with statement before iterating over it, and when the with suite is exited the file could be closed regardless. You may want to mark your iterator as 'done' as well in that case:

_closed = False

def next(self):
    if self._closed:
        raise StopIteration

    line = self.file.readline()
    while line and self.__is_comment(line):
        line = self.file.readline()

    if line:
        working = line.rstrip().split(" ")
        trad, simp = working[0], working[1]
        working = " ".join(working[2:]).split("]")
        pinyin = working[0][1:]
        english = working[1][1:]
        return trad, simp, pinyin, english

    else:
        self.file.close()
        self._closed = True
        raise StopIteration()  

def __enter__(self):
    return self

def __exit__(self, type_, value, tb):
    self.file.close()  # multiple calls to .close() are fine
    self._closed = True
like image 43
Martijn Pieters Avatar answered Sep 21 '22 19:09

Martijn Pieters