Search code examples
pythonfile

how to accept file or path as arguments to method in python


I am trying to write a method that will accept either an opened file

myFile = open("myFile.txt")
obj.writeTo(myFile)
myFile.close()

or a string with a path

obj.writeTo("myFile.txt")

The method is implemented as follows:

def writeTo(self, hessianFile):
    if isinstance(hessianFile,file):
        print("File type")
    elif isinstance(hessianFile,str):
        print("String type")
    else:
        pass

But this raises an error

NameError: global name 'file' is not defined

why is file type not defined? Shouldn't file be defined all the time? How should the implementation be corrected to properly handel both path an file as valid argument types


Solution

  • Don't type check! It's not Pythonic. The core of duck typing is the idea that if it quacks like a duck, it is a duck. The behaviour you want is if it is file-like, it works, and if it is string-like, it works. This isn't just ideological - because this is the standard in Python, people will expect to be able to give you a file-like object and have it work. If you restrict it to only a specific file type, your code will be fragile and inflexible.

    The simplest option is to simply pick the more common outcome, try to work as you would with that, and fail to the other method if you get an exception:

    def writeTo(self, hessianFile):
        try:
            with open(hessianFile, "w") as f:
                do_stuff(f)
        except TypeError:
            do_stuff(hessianFile)
    

    This might seem bad if you are used to other languages where "using exceptions for flow control" is considered bad, but that isn't the case in Python, where they are a core part of the language regularly used that way (every for loop ends with an exception!).

    Or, if you think you are more likely to get a file object most of the time, do it the other way around:

    def writeTo(self, hessianFile):
        try:
            do_stuff(f)
        except AttributeError:
            with open(hessianFile, "w") as f:
                do_stuff(f)
    

    Note my use of the with statement which is the best way of dealing with opening files - it's more readable, and also always closes the file for you, even on exceptions.

    If you really find you have to type check (e.g: the operation is extremely expensive even if it fails, with no way to short-circuit), you should check the string side, as it is easier to work out if something is string-like as opposed to file-like. If you have to check for something file-like, you should implement an abstract base class and look for the functionality you need, rather than actually type-checking.

    The reason your original code failed is that file isn't the base class of objects returned by open() in 3.x.

    The type of file object returned by the open() function depends on the mode. When open() is used to open a file in a text mode ('w', 'r', 'wt', 'rt', etc.), it returns a subclass of io.TextIOBase (specifically io.TextIOWrapper). When used to open a file in a binary mode with buffering, the returned class is a subclass of io.BufferedIOBase. The exact class varies: in read binary mode, it returns a io.BufferedReader; in write binary and append binary modes, it returns a io.BufferedWriter, and in read/write mode, it returns a io.BufferedRandom. When buffering is disabled, the raw stream, a subclass of io.RawIOBase, io.FileIO, is returned. Source

    So for that you want io.FileIO.