0

I would like to understand why using the following snippet leads me to an error:

a) I want to use the following class to create a context manager, as outlined in the link attached below: for me it is very important to keep the "class PrintStop(ExitStack)" form, so please bear in mind when trying to solve this issue, that I already know there are other ways to use ExitStack(), but I am interested in this specific way of using it:

class PrintStop(ExitStack):
    def __init__(self, verbose: bool = False):
        super().__init__()
        self.verbose = verbose

    def __enter__(self):
        super().__enter__()
        if not self.verbose:
            sys.stdout = self.enter_context(open(os.devnull, 'w'))

b) when trying to use the class in the more appropriate way, I get the desired effect to stop all the printing within the "with" block, but when trying to print again after that block I get an error:

with PrintStop(verbose=False):
        print('this shouldn't be printed') <------ok till here

print('this should be printed again as it is outside the with block)  <-----ERROR

c) the error I get is "ValueError: I/O operation on closed file": the reason I guess is the fact that exit method of ExitStack() is not automatically called once we exit the 'with' block, so, how may I change the class to fix this bug?

Here is a quick reference to a similar topic, Pythonic way to compose context managers for objects owned by a class

halfer
  • 19,824
  • 17
  • 99
  • 186
mpv
  • 1
  • It's not that `ExitStack.__exit__` isn't called, it's that it *is* and what it does is close whatever `sys.stdout` refers to without restoring `sys.stdout` to its former value. – chepner Jun 22 '21 at 19:17

1 Answers1

0

ExitStack.__exit__ simply ensures that each context you enter has its __exit__ method called; it does not ensure that any changes you made (like assigning to sys.stdout) inside the corresponding __enter__ is undone.

Also, the purpose of an exit stack is to make it easy to enter contexts that require information not known when the with statement is introduced, or to create a variable number of contexts without having to enumerate them statically.

If you really want to use an exit stack, you'll need something like

class PrintStop(ExitStack):
    def __init__(self, verbose: bool = False):
        super().__init__()
        self.verbose = verbose

    def __enter__(self):
        rv = super().__enter__()
        if not self.verbose:
            sys.stdout = self.enter_context(open(os.devnull, 'w'))
        return rv

    def __exit__(self):
        sys.stdout = sys.__stdout__  # Restore the original
        return super().__exit__()

Keep in mind that contextlib already provides a context manager for temporarily replacing standard output with a different file, appropriately named redirect_stdout.

with redirect_stdout(open(os.devnull, 'w')):
    ...

Using this as the basis for PrintStop makes use of composition, rather than inheritance.

from contextlib import redirect_stdout, nullcontext


class PrintStop:
    def __init__(self, verbose: bool = False):
        super().__init__()
        if verbose:
            self.cm = redirect_stdout(open(os.devnull, 'w'))
        else:
            self.cm = nullcontext()

    def __enter__(self):
        return self.cm.__enter__()

    def __exit__(self):
        return self.cm.__exit__()
chepner
  • 497,756
  • 71
  • 530
  • 681