Question

It seems that checking isinstance(..., io.IOBase) is the 'correct' way to determine if an object is 'file-like'.

However, when defining my own file-like class, it doesn't seem to work:

import io

class file_like():

    def __init__(self):
        pass

    def write(self, line):
        print("Written:", line)

    def close(self):
        pass

    def flush(self):
        pass

print(isinstance(file_like(), io.IOBase))
# Prints 'False'

How can I make it work?

Was it helpful?

Solution 2

Checking isinstance(something, io.IOBase) only checks if something is an instance of an io.IOBase or a class derived from it — so I don't understand where you got the mistaken idea that it's the "correct" way to determine if an object is "file-like".

A different way to do it is with an Abstract Base Class. Python has a number of built-in ones, but currently doesn't have one for "file-like" that could used with isinstance(). However you can define your own by using the abc module as outlined in PEP 3119.

The Python Module of the Week webiste has a good explanation of using the abc module to do things like as this. And this highly rated answer to the question Correct way to detect sequence parameter? shows a similar way of defining your own ABC.

To illustrate applying it to your case, you could define an ABC like this with all its methods abstract — thereby forcing derived classes to define all of them in order to be instantiated:

from abc import ABCMeta, abstractmethod

class ABCFileLike(metaclass=ABCMeta):
    @abstractmethod
    def __init__(self): pass

    @abstractmethod
    def write(self, line): pass

    @abstractmethod
    def close(self): pass

    @abstractmethod
    def flush(self): pass

You could then derive your own concrete classes from it, making sure to supply implementations of all the abstract methods. (If you don't define them all, then a TypeError will be be raised if any attempts are made to instantiate it.)

class FileLike(ABCFileLike):
    """ Concrete implementation of a file-like class.
        (Meaning all the abstract methods have an implementation.)
    """
    def __init__(self):
        pass

    def write(self, line):
        print("Written:", line)

    def close(self):
        pass

    def flush(self):
        pass

print(isinstance(FileLike(), ABCFileLike))  # -> True

You can even add existing classes to it by registering them with the new metaclass:

import io

print(isinstance(io.IOBase(), ABCFileLike))  # -> False

ABCFileLike.register(io.IOBase)
print(isinstance(io.IOBase(), ABCFileLike))  # -> True

OTHER TIPS

isinstance(obj, some_class) just iterates up obj's inheritance chain, looking for some_class. Thus isinstance(file_like, io.IOBase), will be false, as your file_like class doesn't have io.IOBase in its ancestry. file_like doesn't designate an explicit parent, hence it implicitly inherits only from object. That's the only class - besides file_like itself - that will test positive for a file_like instance with isinstance().

What you are doing in file_like is defining the methods expected on a file-like object while not inheriting from any particular "file-like" class. This approach is called duck-typing, and it has many merits in dynamic languages, although it's more popular in others (e.g. Ruby) than Python. Still, if whatever you're providing your file_like instance to follows duck-typing, it should work, provided your file_like does in fact "quack like a file", i.e. behaves sufficiently like a file to not cause errors upon usage at the receiving end.

Of course, if the receiving end is not following duck-typing, for example tries to check types by isinstance() as you do here, this approach will fail.

Finally, a small stylistic nit: don't put empty parens on a class if it doesn't inherit anything explicitly. They are redundant.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top