3

I want to read data from a file without truncating it. I know I can use 'r' for reading.

But I also want to create the file if a FileNotFoundError is raised.

But using 'w', 'w+', 'a+' to create a file works, it also truncates any existing data in the file in later runs.

Basically looking for a replacement for:

try:
    with open('filename', 'r') as file:
        #if no error is raised, file already exits and
        #can be read from: do stuff on file.
except FileNotFoundError:    
    with open('filename', 'x') as file:
        pass
        #no further actions are required here as the file 
        #is just being created

Answer at:

open file for random write without truncating?

states that I should open the file in 'rb+' mode, however 'rb+' raises a FileNotFoundError and does not create the file if it doesn't exist.

opening files in binary mode is also not suited for reading text files.

Elbasel
  • 240
  • 1
  • 10

4 Answers4

1

You could use os.path.exists() to replace the use of FileNotFoundError.

import os


path = '/tmp/test.info'

if os.path.exists(path):
    with open(path, 'r') as infile:
        print(infile.read())
else:
    open(path, 'x')

You could also replace all of the os usage with pathlib if you're on Python 3.4+.

from pathlib import Path


path = Path('/tmp/test.info')

if path.is_file():
    print(path.read_text())
else:
    path.touch()

I'm not really sure whether either of these is much of an improvement, though. These could be reduced to one- or two-liners if it's brevity you want, but they'd be less readable, and still wouldn't be single commands.

kungphu
  • 4,592
  • 3
  • 28
  • 37
  • 2
    Shouldn't you use `try/except` instead, since there's a possibility that the status of the file existence changes between the `if` statement and actually opening/reading/writing the file? – Zain Patel Apr 23 '20 at 23:43
  • 1
    If you're going to make that assumption, you'd also need to use another `try/except` block when attempting `open(path, 'x')`, which throws `FileExistsError` if the file actually is there. `pathlib` doesn't have that issue, but it's still not something I'd call important unless you're on a system where you expect heavy file system interaction with the same files from multiple sources, in which case it should've been mentioned in the question. – kungphu Apr 23 '20 at 23:48
1

The best way I found of doing this is using:

with open(file_name, 'a+') as fd:
  fd.seek(0)
  # do your usual reading here
  stuff = fd.read()

a+ is a read/write mode that doesn't truncate the file, however, it starts reading at the end of the file; which is why seeking to the start of the file is needed afterwards.

-edited to include @lucidbrot improvement.

Elbasel
  • 240
  • 1
  • 10
1

I ended up phrasing the same question for myself as "if the file exists, I want to read it. If it does not, I want a default (empty) string". This is tangential to this Q&A but this page is where you end up if you search for how to do that. In any case, the following will be useful for people who would like to read a file if it exists or else read the empty string.

The desired Pseudocode would be:

  # This does not work
  with open(file_name, "???") as fd:
    stuff = fd.read()

What we can do, however, is this:

# This reads the file, or some default string you choose
try:
  with open(file_name, "r") as fd:
    stuff = fd.read()
except: FileNotFoundError:
    stuff = ""

Comparison to Other Answers

Elbasel's Answer is great. It does the same thing but also creates the file if it does not exist yet.
It can be improved by using a context manager though, to ensure you don't forget to close the file:

with open(file_name, 'a+') as fd:
  fd.seek(0)
  # do your usual reading here
  stuff = fd.read()
lucidbrot
  • 5,378
  • 3
  • 39
  • 68
0

You could try something like this:

>>> try:
...   t = open('filename', 'r+')
... except FileNotFoundError:
...   t = open('filename', 'x')
...
>>> with t as file:
...   file.write("Testing:\n")
...
9

Assigning open() to a name and then using that name in a with statement also works

Josh J
  • 6,813
  • 3
  • 25
  • 47