132

I want to read a .csv file in python.

  • I don't know if the file exists.
  • My current solution is below. It feels sloppy to me because the two separate exception tests are awkwardly juxtaposed.

Is there a prettier way to do it?

import csv    
fName = "aFile.csv"

try:
    with open(fName, 'r') as f:
        reader = csv.reader(f)
        for row in reader:
            pass #do stuff here
    
except IOError:
    print "Could not read file:", fName
Charles Holbrow
  • 3,967
  • 6
  • 30
  • 35
  • 2
    If a non-existing file is not an error case but a likely circumstance then checking for and handling its absence/non-readability explicitly before (and *additionally* to) the `try` might be worth it. This can be done with `os.path.exists(file)` and `os.access(file, os.R_OK)` respectively. Such check can never be free from a race condition though but vanishing files are seldom a normal circumstance ;) – stefanct Apr 08 '17 at 14:50
  • 3
    The answers to this question should probably be updated to include usage of the `pathlib` module, which makes this problem a lot easier, and should probably be standard Python practice (especially since it was also backported to 2.7). – Rick Jul 03 '17 at 13:56
  • 1
    while this catches ```IOError```, it does not catch ```csv.Error```due to file not being CSV format when ```Dialect.strict=True```or ```Error``` for any other errors (according to CSV package docs), so an outer try, or just simply checking for file exists, then an inner try for CSV exceptions is probably the right answer. – pink spikyhairman May 28 '20 at 13:41
  • @pinkspikyhairman Yes, In your except handler, you do have to decide which error types you want to handle. See here for how to handle multiple specific types of errors: https://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block – Charles Holbrow May 29 '20 at 00:42
  • 2
    You can also create the file if it doesn't exist with 'r+' mode. This prevents file not existing errors. – Quinten C May 07 '22 at 08:28

7 Answers7

91

How about this:

try:
    f = open(fname, 'rb')
except OSError:
    print "Could not open/read file:", fname
    sys.exit()

with f:
    reader = csv.reader(f)
    for row in reader:
        pass #do stuff here
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
  • 19
    The only problem with this is that the file is opened outside of the `with` block. So if an exception occurs between the `try` block containing the call to `open` and the `with` statement, the file doesn't get closed. In this case, where things are very simple, it's not an obvious issue, but it could still pose a danger when refactoring or otherwise modifying the code. That being said, I don't think there's a better way to do this (other than the original version). – intuited Apr 11 '11 at 21:12
  • 10
    @intuited: That's right. In fact, the final answer to the OP is probably just: No, the way you've done it is the right way. – jscs Apr 11 '11 at 21:20
  • 1
    `FileNotFoundError.mro()` is `[, , , , ]` and `IOError.mro()` is `[, , , ]`. How about using either `OSError` or `Exception` instead? ``` – hotohoto Oct 28 '19 at 08:25
  • 1
    @hotohoto: Good idea. I'm not sure - perhaps the Exception hierarchy has changed in this regard since 2011, but anyway your suggestion is more encompassing. – Tim Pietzcker Oct 28 '19 at 14:47
  • How about making the "with f:" part of an "else:" clause for the exception? The spoken english version of this would be, "try and open, if error raise error, else do normal processing" Block code samples aren't allowed in comments, so I can't show it. – Ben Slade Apr 06 '21 at 21:16
  • Hi I tried to update your answer, but I got an edit queue is full error. So I had to write a new one. https://stackoverflow.com/a/68175784/1638350 – atevm Jun 29 '21 at 09:14
  • This is indeed good way, in exception the program itself terminated.. then not to worry on further whats done. however if you are not exiting, then normal way how the question is asked itself can be the best. –  Aug 09 '21 at 06:07
  • Can someone verify if Ben Slade's suggestion is the way to do it? Like try it, except something happens, else continue with f. – Seven Jun 12 '22 at 16:20
25

Here is a read/write example. The with statements insure the close() statement will be called by the file object regardless of whether an exception is thrown. http://effbot.org/zone/python-with-statement.htm

import sys

fIn = 'symbolsIn.csv'
fOut = 'symbolsOut.csv'

try:
   with open(fIn, 'r') as f:
      file_content = f.read()
      print "read file " + fIn
   if not file_content:
      print "no data in file " + fIn
      file_content = "name,phone,address\n"
   with open(fOut, 'w') as dest:
      dest.write(file_content)
      print "wrote file " + fOut
except IOError as e:
   print "I/O error({0}): {1}".format(e.errno, e.strerror)
except: #handle other exceptions such as attribute errors
   print "Unexpected error:", sys.exc_info()[0]
print "done"
JayS
  • 2,057
  • 24
  • 16
  • In this case, IOError is obvious but when will general exception occur from code coverage point of view. How can I make a test case to generate a general exception. – Dr. Mian Jan 07 '21 at 10:01
9

How about adding an "else" clause to the exception and putting the "with" statement in the "else" section? Like this:

try:
  f = open(fname, 'rb')
except FileNotFoundError:
    print(f"File {fname} not found.  Aborting")
    sys.exit(1)
except OSError:
    print(f"OS error occurred trying to open {fname}")
    sys.exit(1)
except Exception as err:
    print(f"Unexpected error opening {fname} is",repr(err))
    sys.exit(1)  # or replace this with "raise" ?
else:
  with f:
    reader = csv.reader(f)
    for row in reader:
        pass #do stuff here

Instead of sys.exit(), you could put 'raise' and escalate the error up the chain. It might be better to get system info about the error from the top level error handler.

Ben Slade
  • 478
  • 5
  • 11
  • I have a beginner's question. Why did you have to put the continuation of file opening inside else block? Do you really have to do that instead of just continuing with f outside the try-except statement? – Seven Jun 12 '22 at 16:27
  • @Hansel it's a good guard against someone refactoring to put putting code in between the open and with. If someone were to do that an the code errored, then the file would be stuck open. – Rick Jul 10 '22 at 20:57
7

In Python 3, IOError is an alias of OSError. To verify, run the code:

IOError is OSError
---
True

OSError is the parent class of the file I/O exceptions.

      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
OSError.__subclasses__()
---
[ConnectionError,
 BlockingIOError,
 ChildProcessError,
 FileExistsError,
 FileNotFoundError,
 IsADirectoryError,
 NotADirectoryError,
 InterruptedError,
 PermissionError,
 ProcessLookupError,
 TimeoutError,
 io.UnsupportedOperation,
 signal.ItimerError,
 socket.herror,
 socket.gaierror,
 socket.timeout,
 ssl.SSLError,
 shutil.Error,
 shutil.SpecialFileError,
 shutil.ExecError,
 shutil.ReadError,
 urllib.error.URLError,
 gzip.BadGzipFile]

Hence, catch the OSError and check the exact class if detail is requied.

try:
    with open('hoge') as f:
        pass
except OSError as e:
    print(f"{type(e)}: {e}")
---
<class 'FileNotFoundError'>: [Errno 2] No such file or directory: 'hoge'
mon
  • 18,789
  • 22
  • 112
  • 205
6

I updated Tim Pietzcker answer, because it uses Python 2 which is not maintained anymore.

I tried to edit the answer first, but I got: edit queue is full, so I could not.

import errno

fname = "no_such_a_file.txt"

try:
    f = open(fname, 'rb')
except OSError as e:
    if e.errno == errno.ENOENT:
        print(
            f"No such a file or directory (errno: { e.errno }):",
            fname, file=sys.stderr
        )
    else:
        # for other OS errno codes you may want to write
        # your more specific error messages which
        print(
            f"Cannot oppen file (errno: { e.errno } ):",
            fname,
            file=sys.stderr
        )
    sys.exit(os.EX_OSFILE)

with f:
    reader = csv.reader(f)
    for row in reader:
        pass #do stuff here

I also made some minor improvements:

The code is self-contained.

You should check the errno number of your exception, which helps you narrow down the error

You should write error and log messages into sys.stderr and not into sys.stdout (default for print), because then you can redirect your error messages into a different file.

You should return a non-zero exit code (documented here) which is a must if you want to make your python code usable in a Unix environment, such as a shell script:

#!/usr/bin/env bash
set -euo pipefail

if ./read_file.py 2> error.log
then
    echo "do stuff"
else
    exit_code=$?
    echo "file is not readable, exit code: $exit_code" > /dev/stderr
    exit $exit_code
fi
atevm
  • 801
  • 1
  • 14
  • 27
1
fname = 'filenotfound.txt'
try:
    f = open(fname, 'rb')
except FileNotFoundError:
    print("file {} does not exist".format(fname))

file filenotfound.txt does not exist

exception FileNotFoundError Raised when a file or directory is requested but doesn’t exist. Corresponds to errno ENOENT.

https://docs.python.org/3/library/exceptions.html
This exception does not exist in Python 2.

  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Donald Duck Jun 19 '20 at 17:18
-14

Adding to @Josh's example;

fName = [FILE TO OPEN]
if os.path.exists(fName):
    with open(fName, 'rb') as f:
        #add you code to handle the file contents here.
elif IOError:
    print "Unable to open file: "+str(fName)

This way you can attempt to open the file, but if it doesn't exist (if it raises an IOError), alert the user!

Zac Brown
  • 5,905
  • 19
  • 59
  • 107
  • Not seeing the problem. If it was incorrect syntax it would raise a syntax error when executed! – Zac Brown Apr 11 '11 at 21:22
  • 8
    Not a syntax error, but `bool(IOError)` is simply `True` and `if` doesn't catch any exception. –  Apr 11 '11 at 21:23
  • 10
    `>>> if IOError: print "That's not an exception handler"` – jscs Apr 11 '11 at 21:25
  • 4
    @Josh Caswell is correct. IOError evaluates to True. https://docs.python.org/2.4/lib/truth.html – hecvd Jun 29 '16 at 21:40