Cat
Cat

Reputation: 55

Why can I not redirect STDOUT using "from sys import stdout"?

I'm trying to redirect the STDOUT of a python script to a file.

If STDOUT is imported from sys, the script's output does not get redirected to a file:

from sys import stdout
stdout = open("text", "w")
print("Hello")

However, if I import only sys and use sys.stdout, the script's output is successfully redirected:

import sys
sys.stdout = open("text", "w")
print("Hello")

Why is this? According to this answer the only difference between "import X" and "from X import Y" is the name that is bound. How does this manage to affect stdout?

Upvotes: 3

Views: 777

Answers (4)

snakes_on_a_keyboard
snakes_on_a_keyboard

Reputation: 884

You can save yourself some effort and earn yourself some "Pythonic" points with this little trick:

import sys
print('hello', file=sys.stdout)

Of course, print already goes to sys.stdout by default, so maybe I'm missing something. I'm not sure what's going on with the open('text', 'w'), but it might not be necessary if you do it this way :)

In answer to your question about the variable assignment impact, when you use the = operator on a variable, you are actually assigning it to the value in the scope dictionary (in this case globals).

So when you import sys, sys gets imported in the globals dictionary.

So globals looks like,

{...,
'sys': <module 'sys' (built-in)>}

You can think of the module itself as a dictionary. So when you do sys.stdout= ... that's like doing globals()['sys'].__dict__['stdout'] =...

When you just import stdout, globals looks like:

{...,
'stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>}

Thus when you do stdout=... you're really directly replacing that key in the dictionary:

globals()['stdout'] = ...

Hopefully that helps add a little clarity!

Upvotes: 0

user9633488
user9633488

Reputation:

The way I do it is to create a contextmanager.

@contextmanager
def suppress_stdout():
    with open(os.devnull, 'w') as devnull:
        old_stdout = sys.stdout
        old_stderr = sys.stderr
        sys.stdout = devnull
        try:
            yield
        finally:
            sys.stdout = old_stdout
            sys.stderr = old_stderr

and then when I want to suppress the stdout on a certain command:

with suppress_stdout():
    # suppressed commands    

Upvotes: 1

viraptor
viraptor

Reputation: 34145

It's the same as:

x = some_object.some_attr
x = open(...)

You're not changing some_object.some_attr in that case. You're just assigning to a local value.

When you use sys.stdout = ... you're actually updating the stdout.

Upvotes: 1

abarnert
abarnert

Reputation: 365657

Yes, the only difference is that the name Y is bound to X.Y.

Either way, binding Y to something else isn't going to affect anything in X.


If it makes it easier, consider this parallel:

>>> y = 2
>>> x = y
>>> x = 3

Do you expect this to change y to 3? Of course not. But that's exactly the same thing you're doing.


If it's still not clear, let's break down what those imports actually do.

When you import sys, it's equivalent to:

sys.modules['sys'] = __import__('sys')
sys = sys.modules['sys']
sys.stdout = open(text, "w")

But with the from sys import stdout:

sys.modules['sys'] = __import__('sys')
stdout = sys.modules['sys'].stdout
stdout = open(text, "w")

Upvotes: 4

Related Questions