2 comments

[ 3.4 ms ] story [ 17.2 ms ] thread

    >>> with open("using_python_to_profit") as f:
    ...     first, *_, last = f.readlines()
Or more simply:

    first, *_, last = f
Though I don't like how '_' can keep a potentially arbitrary amount of data in memory.

There's also a question of what to do when there is only one line in the file, or zero lines. The above raises a ValueError. An alternative solution, which uses None if there are no lines, and make first == last if there is one line, and which works with Python 2.6 and later, is:

    with open("using_python_to_profit") as f:
        first = last = next(f, None)
        if first is not None:
            for last in f:
                pass
The test for 'first is not None' is because next(f) can raise a StopIteration, but if the file is written to again, next(f) will return the next line.

    % touch empty.txt
    % ls -l empty.txt 
    -rw-r--r--  1 dalke  admin  0 Feb 11 01:34 empty.txt
    % python
    Python 3.3.0 (default, Mar 12 2013, 09:53:08) 
    [GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.54)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> f = open("empty.txt")
    >>> next(f)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    >>> ^Z
    Suspended
    % echo "Hello!" >> empty.txt 
    % fg
    python
    
    >>> next(f)
    'Hello!\n'
Yes, according to the iterator specification, once an iterator raises a StopIteration exception, subsequent calls are always supposed to raise StopIteration. According to the documentation, file iterators are "broken". Quoting from https://docs.python.org/3.4/library/stdtypes.html :

    Once an iterator’s __next__() method raises StopIteration, it must continue
    to do so on subsequent calls. Implementations that do not obey this property
    are deemed broken.

Sigh. Time to report that bug. Well, need to reproduce it with the most recent Python first.