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

10,353

Solution 1

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.

Solution 2

Don't use the name 'file' (which is a builtin object in python) as the name for your file-like object.

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

Example:

>>> filetype = lambda x: isinstance(x, file)
>>> file = open('t','w')
>>> filetype(file)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

>>> f = open('t','w')
>>> del file
>>> filetype(f)
True

Solution 3

There is not file type in Python 3. It was built-in in Python 2, but it disappeared in Python 3. Compare the following:

Python 2.7.1 [...]
>>> f = open('zz.bak', 'w')
>>> type(f)
<type 'file'>
>>> print f.__doc__
file(name[, mode[, buffering]]) -> file object

Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),
writing or appending.  The file will be created if it doesn't exist
when opened for writing or appending; it will be truncated when
opened for writing.  Add a 'b' to the mode for binary files.
Add a '+' to the mode to allow simultaneous reading and writing.
If the buffering argument is given, 0 means unbuffered, 1 means line
buffered, and larger numbers specify the buffer size.  The preferred way
to open a file is with the builtin open() function.
Add a 'U' to mode to open the file for input with universal newline
support.  Any line ending in the input file will be seen as a '\n'
in Python.  Also, a file so opened gains the attribute 'newlines';
the value for this attribute is one of None (no newline read yet),
'\r', '\n', '\r\n' or a tuple containing all the newline types seen.

'U' cannot be combined with 'w' or '+' mode.

While in Python 3...

Python 3.2.1 [...]
>>> f = open('xx', 'w')
>>> type(f)
<class '_io.TextIOWrapper'>
>>> print(f.__doc__)
Character and line based layer over a BufferedIOBase object, buffer.

encoding gives the name of the encoding that the stream will be
decoded or encoded with. It defaults to locale.getpreferredencoding.

errors determines the strictness of encoding and decoding (see the
codecs.register) and defaults to "strict".

newline can be None, '', '\n', '\r', or '\r\n'.  It controls the
handling of line endings. If it is None, universal newlines is
enabled.  With this enabled, on input, the lines endings '\n', '\r',
or '\r\n' are translated to '\n' before being returned to the
caller. Conversely, on output, '\n' is translated to the system
default line seperator, os.linesep. If newline is any other of its
legal values, that newline becomes the newline when the file is read
and it is returned untranslated. On output, '\n' is converted to the
newline.

If line_buffering is True, a call to flush is implied when a call to
write contains a newline character.
Share:
10,353
Andrew Hoos
Author by

Andrew Hoos

I develop and maintain automated testing infrastructure.

Updated on June 04, 2022

Comments

  • Andrew Hoos
    Andrew Hoos almost 2 years

    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

  • Andrew Hoos
    Andrew Hoos almost 12 years
    I made the change you suggested, but it did not fix my problem. I noticed that your error says that arg2 is the wrong type where my error states that the second argument is undefined. This is python3.