sliceOfPi
sliceOfPi

Reputation: 11

Restoring redirected sys.stdout and sys.stderr producing odd results

After looking through other stackoverflow posts, I can't seem to figure this redirection problem out. What I would like to do is suppress both stdout and stderr, and then restore them after the error is caught. The suppression works fine, but restoring them only half works.

If I try to suppress and restore stderr and stdout, the suppression works but not the restoring. If I only try to suppress/restore stdout it works (but then I get all the stderr text I don't want).

I can't reason out why there would be any difference causing stderr to block stdout from being restored, hoping for input on why that might be (or if I'm doing something weird/stupid)

Here's the code that I want to work but only suppresses out/err and doesn't restore ('Restored stdout' never prints):

sys.stdout = None
sys.stderr = None
try:
    op_args = op_parse.parse_args(selection.split(' '))
except SystemExit:
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__
    print("Restored stdout")

Here's the code that suppresses stdout AND restores it (at the cost of printing the pesky stderr):

sys.stdout = None
#sys.stderr = None
try:
    op_args = op_parse.parse_args(selection.split(' '))
except SystemExit:
    sys.stdout = sys.__stdout__
    #sys.stderr = sys.__stderr__
    print("Restored stdout")

Edit: I figured out a workaround, but I'm still interested in why the above problem occurs. My workaround is to reassign stdout/stderr to = open("/dev/null", "w") which does produce the behavior I want. Again I still would like input on the original problem.

Upvotes: 1

Views: 1079

Answers (2)

martineau
martineau

Reputation: 123423

You can do this a little more elegantly by creating a context manager:

import os
from contextlib import contextmanager

@contextmanager
def nullout():
    save_stdout = sys.stdout
    save_stderr = sys.stderr
    sys.stdout = open(os.devnull, 'w')
    sys.stderr = open(os.devnull, 'w')
    try:
        yield
    finally:
        sys.stdout = save_stdout
        sys.stderr = save_stderr

with nullout():
    op_args = op_parse.parse_args(selection.split(' '))

A nice thing about context managers is that the code after the yield will get executed regardless of whether an exception occurs or not.

The reason you can't just set sys.stdout and sys.stderrto None is probably because None doesn't have a write() or close() method and generally doesn't behave like an output stream.

Upvotes: 4

Cireo
Cireo

Reputation: 4427

As a high level comment, this technique is error prone and usually means something else is going wrong. For example - restoring in the except above (instead of in a finally) means you restore stdout and stderr only if you fail.

I suspect that your observed problem may be due to your op_parse invocation. Consider the below code

from __future__ import print_function
import sys

sys.stdout = None
sys.stderr = None
try:
    print('before', file=sys.stdout)
    print('before', file=sys.stderr)
    # sys.stderr.write('hi')
    assert False
except AssertionError:
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__
    print("Restored stdout", file=sys.stdout)

When I run it, I see: Restored stdout.

If I uncomment the sys.stderr.write line, I see: lost sys.stderr.

A safer approach would be to use a context to redirect stdout/err to /dev/null or an IOStream during your operation - or better yet, find the validation function being used internally and call that directly instead of looking for a system exit on a high level function (is op_parse == opt_parse?)

Upvotes: 0

Related Questions