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()  
                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).
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:
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.
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.
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 = ....
                        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
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With