Search code examples
pythonfileclassisinstance

How to make a file-like class work with "isinstance(cls, io.IOBase)"?


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?


Solution

  • 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