3

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.

Ogen
  • 6,499
  • 7
  • 58
  • 124
  • 2
    What do you mean "any string I want"? – TerryA Aug 30 '17 at 00:40
  • 1
    Yes. As long as you put anything you want inside the file, you'll get whatever you want. – cs95 Aug 30 '17 at 00:42
  • @TerryA Please see my edit hopefully that clarifies things – Ogen Aug 30 '17 at 00:46
  • I might be understanding this wrong, but wouldn't that defy the purpose of `open`? – Luke Aug 30 '17 at 00:46
  • Are you specifically wanting to override the `open` function, or to have some other function that works with the `with .. as` statement? – Hamms Aug 30 '17 at 00:47
  • I'm still a little confused... can you give me an example? – TerryA Aug 30 '17 at 00:47
  • I have added more clarification with an example. – Ogen Aug 30 '17 at 00:53
  • Is it only for testing purpose or you want to do this in actual code? – Ashwini Chaudhary Aug 30 '17 at 00:54
  • as a general rule, if you find yourself wanting to rewrite built-in functions in order to test your code, that's a pretty good sign that you need to refactor either your tests or your code – Hamms Aug 30 '17 at 00:57
  • that being said, there does exist a helper in the `mock` library to do just this: http://www.voidspace.org.uk/python/mock/helpers.html?highlight=open#mock.mock_open – Hamms Aug 30 '17 at 00:59

3 Answers3

9

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())
Zach Gates
  • 4,045
  • 1
  • 27
  • 51
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
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
1

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

Erich
  • 1,902
  • 1
  • 17
  • 23