'Override python open function when used with the 'as' keyword to print anything

How can I override the built in open function such that when I call it like so...

with open(file_path, "r") as f:
    contents = f.read()

The contents variable is any string I want?

EDIT: To clarify, I want to be able to just provide a string to the open function rather than a file path that will be read.

with open("foobar") as f:
    contents = f.read()
    print(contents)

The above should print foobar.

I am aware this is defeating the purpose of open etc but it is for testing purposes.



Solution 1:[1]

You can create your own file-like type and override the builtin open with your own open function.

import builtins
import contextlib


class File(object):
    """
    A basic file-like object.
    """

    def __init__(self, path, *args, **kwargs):
        self._fobj = builtins.open(path, *args, **kwargs)

    def read(self, n_bytes = -1):
        data = self._fobj.read(n_bytes)
        ...
        return data

    def close(self):
        self._fobj.close()


@contextlib.contextmanager
def open(path, *args, **kwargs):
    fobj = File(path, *args, **kwargs)
    try:
        with contextlib.closing(fobj):
            yield fobj
    finally:
        pass

You can add whatever behavior or additional logic needed to adjust the return value of read() inside File.read itself, or override the behavior entirely from a subclass of File.


Simplified for the particular case in question:

class File(str):
    def read(self):
        return str(self)


@contextlib.contextmanager
def open(string):
    try:
        yield File(string)
    finally:
        pass


with open('foobar') as f:
    print(f.read())

Solution 2:[2]

Considering it is for testing purpose and you want to force the open calls to return a specific string then you can use mock_open here.

Let's say I have a module foo that has a function that reads content from a file and counts the number of lines:

# foo.py
def read_and_process_file():
    with open('Pickle Rick') as f:
        contents = f.read()
    print('File has {n} lines'.format(n=len(contents.splitlines())))

Now in your test you can mock the open for this module and make it return any string you want:

from unittest.mock import mock_open, patch
import foo

m = mock_open(read_data='I am some random data\nthat spans over 2 lines')
with patch('foo.open', m):
    foo.read_and_process_file()  # prints 2

Solution 3:[3]

You can design your own class, as with requires an object with a defined __enter__ and __exit__ method. As that is what with does.

class my_class:
    def __init__(self, *args):
        print("initializing variable, got args: {}".format(args))
    def __enter__(self):
        print("Inside enter statement!")
        return "arbitrary text"
    def __exit__(self, type, value, traceback):
        print("closing time, you don't have to go home")
        return
with my_class(1,2,3) as my_thing:
    print("inside the with block!")
    print("The return from my_class __enter__ is: ", my_thing)


print("Outside with block!")

output when ran:

initializing variable, got args: (1, 2, 3)
Inside enter statement!
inside the with block!
The return from my_class __enter__ is:  arbitrary text
closing time, you don't have to go home
Outside with block!

More reading here: http://effbot.org/zone/python-with-statement.htm

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3