Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function that accepts file object or path

Tags:

python

file-io

I want to write a function that accepts either a path as a string or a file object. So far I have:

def awesome_parse(path_or_file):
    if isinstance(path_or_file, basestring):
        f = open(path_or_file, 'rb')
    else:
        f = path_or_file
    with f as f:
        return do_stuff(f)

where do_stuff takes an open file object.

Is there a better way to do this? Does with f as f: have any repercussions?

Thanks!

like image 843
TorelTwiddler Avatar asked Jul 21 '11 22:07

TorelTwiddler


People also ask

How do you pass a file path as an argument in Python?

To use it, you just pass a path or filename into a new Path() object using forward slashes and it handles the rest: Notice two things here: You should use forward slashes with pathlib functions. The Path() object will convert forward slashes into the correct kind of slash for the current operating system.

Can you open a file in a function Python?

Python has a built-in open() function to open a file. This function returns a file object, also called a handle, as it is used to read or modify the file accordingly. We can specify the mode while opening a file. In mode, we specify whether we want to read r , write w or append a to the file.


2 Answers

The odd thing about your code is that if it is passed an open file, it will close it. This isn't good. Whatever code opened the file should be responsible for closing it. This makes the function a bit more complex though:

def awesome_parse(path_or_file):
    if isinstance(path_or_file, basestring):
        f = file_to_close = open(path_or_file, 'rb')
    else:
        f = path_or_file
        file_to_close = None
    try:
        return do_stuff(f)
    finally:
        if file_to_close:
            file_to_close.close()

You can abstract this away by writing your own context manager:

@contextlib.contextmanager
def awesome_open(path_or_file):
    if isinstance(path_or_file, basestring):
        f = file_to_close = open(path_or_file, 'rb')
    else:
        f = path_or_file
        file_to_close = None

    try:
        yield f
    finally:
        if file_to_close:
            file_to_close.close()

def awesome_parse(path_or_file):
    with awesome_open(path_or_file) as f:
        return do_stuff(f)
like image 107
Ned Batchelder Avatar answered Oct 28 '22 22:10

Ned Batchelder


You could do:

def awesome_parse(do_stuff):
    """Decorator to open a filename passed to a function
       that requires an open file object"""
    def parse_path_or_file(path_or_file):
        """Use a ternary expression to either open the file from the filename
           or just pass the extant file object on through"""
        with (open(path_or_file, 'rb') 
               if isinstance(path_or_file, basestring) 
                else path_or_file) as f:
            return do_stuff(f)
    return parse_path_or_file

And then when you declare any function that does stuff on an open file object:

@awesome_parse
def do_things(open_file_object):
    """This will always get an open file object even if passed a string"""
    pass

@awesome_parse
def do_stuff(open_file_object):
    """So will this"""
    pass

Edit 2: More detailed info on the decorator.

like image 43
agf Avatar answered Oct 28 '22 20:10

agf