shadyabhi
shadyabhi

Reputation: 17234

Best way to read file contents of a file and set to empty string if exception happen

try:
    content = open("/tmp/out").read()
except:
    content = ""

Can I go any shorter or more elegant than this? I've to do it for more than one files so I want something more short.

Is writing function the only shorter way to do it?

What I actually want is this but I want to concat "" if there is any exception

lines = (open("/var/log/log.1").read() + open("/var/log/log").read()).split("\n")

Upvotes: 2

Views: 246

Answers (4)

jfs
jfs

Reputation: 414385

You could define a function to catch errors:

from itertools import chain

def readlines(filename):
    try:
        with open(filename) as file:
            return file.readlines() # or just `file` to return an iterator 
    except EnvironmentError:
        return []

files = (readlines(name) for name in ["/var/log/1", "/var/log/2"])
lines = list(chain.from_iterable(files))

Upvotes: -1

sotapme
sotapme

Reputation: 4903

This code will monkey patch out open for another open that creates a FakeFile that always returns a "empty" string if open throws an `IOException``.

Whilst it's more code than you'd really want to write for the problem at hand, it does mean that you have a reusable context manager for faking open if the need arises again (probably twice in the next decade)

with monkey_patched_open():
     ...

Actual code.

#!/usr/bin/env python
from contextlib import contextmanager
from StringIO import StringIO
################################################################################
class FakeFile(StringIO):
    def __init__(self):
        StringIO.__init__(self)
        self.count = 0

    def read(self, n=-1):
        return "<empty#1>"

    def readlines(self, sizehint = 0):
        return ["<empty#2>"]

    def next(self):
        if self.count == 0:
            self.count += 1
            return "<empty#3>"
        else:
            raise StopIteration
################################################################################
@contextmanager
def monkey_patched_open():
    global open
    old_open = open

    def new_fake_open(filename, mode="r"):
        try:
            fh = old_open(filename, mode)
        except IOError:
            fh = FakeFile()
        return fh

    open = new_fake_open

    try:
        yield
    finally:
        open = old_open

################################################################################
with monkey_patched_open():
    for line in open("NOSUCHFILE"):
        print "NOSUCHFILE->", line
    print "Other", open("MISSING").read()
    print "OK", open(__file__).read()[:30]

Running the above gives:

NOSUCHFILE-> <empty#3>
Other <empty#1>
OK #!/usr/bin/env python
from co

I left in the "empty" strings just to show what was happening.

StringIO would have sufficed just to read it once but I thought the OP was looking to keep reading from file, hence the need for FakeFile - unless someone knows of a better mechanism.

I know some see monkey patching as the act of a scoundrel.

Upvotes: 0

Pavel Anossov
Pavel Anossov

Reputation: 62928

Yes, you'll have to write something like

def get_contents(filename):
    try:
        with open(filename) as f:
            return f.read()
    except EnvironmentError:
        return ''


lines = (get_contents('/var/log/log.1')
         + get_contents('/var/log/log')).split('\n')

NlightNFotis raises a valid point, if the files are big, you don't want to do this. Maybe you'd write a line generator that accepts a list of filenames:

def get_lines(filenames):
    for fname in filenames:
        try:
           with open(fname) as f:
               for line in f:
                   yield line
        except EnvironmentError:
            continue


...

for line in get_lines(["/var/log/log.1", "/var/log/log"]):
    do_stuff(line)

Another way is to use the standard fileinput.FileInput class (thanks, J.F. Sebastian):

import fileinput

def eat_errors(f, mode):
    try:
        return open(f, mode)
    except IOError:
        return open(os.devnull)

for line in fileinput.FileInput(["/var/log/log.1", "/var/log/log"], openhook=eat_errors):
    do_stuff(line)

Upvotes: 3

Mr_Spock
Mr_Spock

Reputation: 3835

You could try the following, but it's probably not the best:

import os

def chk_file(filename):
    if os.stat(filename).st_size == 0:
        return ""
    else:
        with open(filename) as f:
            return f.readlines()


if __name__=="__main__":
    print chk_file("foobar.txt") #populated file
    print chk_file("bar.txt") #empty file
    print chk_file("spock.txt") #populated

It works. You can wrap it with your try-except, if you want.

Upvotes: 0

Related Questions