0

I am trying to open a file read it's content and write to it by using the contents that were read earlier. I am opening the file in 'a+' mode. I can't use 'r+' mode since it won't create a file if it doesn't exist.

TheWhiteFang
  • 745
  • 5
  • 16
  • If you know how to solve it using `r+`, why don't precede that part with a check if the file exists using `os.path.exists()`, and [create](https://stackoverflow.com/questions/12654772/create-empty-file-using-python) it if it doesn't. – Thymen Dec 12 '20 at 14:45
  • 1
    @Thymen I am doing it now, but I was wondering how can you do this. There will be some scenarios where it will be useful. – TheWhiteFang Dec 12 '20 at 16:03

2 Answers2

1

a+ will put the pointer in the end of the file.

You can save it with tell() for later writing.

Then use seek(0,0) to return to file beginning for reading.

tell()

seek()

Lior Cohen
  • 5,570
  • 2
  • 14
  • 30
0

Default open

Using the default a(+) option, it is not possible, as provided in the documentation:

''mode is an optional string that specifies the mode in which the file is opened. It defaults to 'r' which means open for reading in text mode. Other common values are 'w' for writing (truncating the file if it already exists), 'x' for creating and writing to a new file, and 'a' for appending (which on some Unix systems, means that all writes append to the end of the file regardless of the current seek position).''


Alternative

Using the default open, this is not possible.However we can of course create our own file handler, that will create a file in r and r+ mode when it doesn't exists.

A minimal working example that works exactly like open(filename, 'r+', *args, **kwargs), would be:

import os


class FileHandler:
    def __init__(self, filename, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
        self.filename = filename
        self.mode = mode

        self.kwargs = dict(buffering=buffering, encoding=encoding, errors=errors, newline=newline, closefd=closefd)
        if self.kwargs['buffering'] is None:
            del self.kwargs['buffering']
    def __enter__(self):
        if self.mode.startswith('r') and not os.path.exists(self.filename):
            with open(self.filename, 'w'): pass
        self.file = open(self.filename, self.mode, **self.kwargs)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()

Now when you use the following code:

with FileHandler("new file.txt", "r+") as file:
    file.write("First line\n")
    file.write("Second line\n")
    file.seek(0, 0)
    file.write("Third line\n")

It will generate a new file new file.txt, when it doesn't exists, with the context:

Third line
Second line

If you would use the open you will receive a FileNotFoundError, if the file doesn't exists.


Notes

  • I am only creating a new file when the mode starts with an r, all other files are handled as would be by the normal open function.
  • For some reason passing buffering=None, directly to the open function crashes it with an TypeError: an integer is required (got type NoneType), therefore I had to remove it from the key word arguments if it was None. Even though it is the default argument according to the documentation (if any one knows why, please tell me)

Edit

The above code didn't handle the following cases:

file = FileHandler("new file.txt", "r+")
file.seek(0, 0)
file.write("Welcome")
file.close()

In order to support all of the open use cases, the above class can be adjusted by using __getattr__ as follows:

import os


class FileHandler:
    def __init__(self, filename, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
        self.filename = filename
        self.mode = mode

        self.kwargs = dict(buffering=buffering, encoding=encoding, errors=errors, newline=newline, closefd=closefd)
        if self.kwargs['buffering'] is None:
            del self.kwargs['buffering']

        if self.mode.startswith('r') and not os.path.exists(self.filename):
            with open(self.filename, 'w'): pass
        self.file = open(self.filename, self.mode, **self.kwargs)

    def __enter__(self):
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()

    def __getattr__(self, item):
        if hasattr(self.file, item):
            return getattr(self.file, item)
        raise AttributeError(f"{type(self).__name__}, doesn't have the attribute {item!r}")
Thymen
  • 2,089
  • 1
  • 9
  • 13