Search code examples
pythonpython-3.xclasspython-3.5contextmanager

Use contextmanager inside init


In the code below I don't understand why the with super().__init__(*args, **kwargs): line in MyFileIO2 is throwing an error about missing __exit__ while everything works perfectly fine with the MyFileIO class. I don't really understand what exactly the difference between doing the with inside or outside of the init is. Can someone enlighten me what is going on here?

import io

class MyFileIO(io.FileIO):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __enter__(self, *args, **kwargs):
        f = super().__enter__(*args, **kwargs)
        print('first byte of file: ', f.read(1))
        return f

class MyFileIO2(io.FileIO):
    def __enter__(self, *args, **kwargs):
        f = super().__enter__(*args, **kwargs)
        print('first byte of file: ', f.read(1))
        return f

    def __init__(self, *args, **kwargs):
        with super().__init__(*args, **kwargs): # AttributeError: __exit__
            pass

path = 'some_file.bin'

with MyFileIO(path, 'rb'):
    pass

MyFileIO2(path, 'rb')

Solution

  • You will need to call the context manager on self, because __init__ doesn't actually return anything.

    class MyFileIO2(io.FileIO):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            with self:
                 pass
    
        def __enter__(self, *args, **kwargs):
            f = super().__enter__(*args, **kwargs)
            print('First byte of file: ', f.read(1))
            return f
    

    For testing, I created a binary file having the contents "hello world".

    _ = MyFileIO2(path, 'rb')    
    # First byte of file:  b'h'
    

    What happens is the return value of super().__init__ is being passed through the context manager, so you effectively have this:

    with None:
         pass
    
    AttributeError: __enter__
    

    The context manager tries calling the __enter__ method on the NoneType object, but that is an invalid operation.