3

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?

Cat
  • 55
  • 8

4 Answers4

4

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")
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    Ah, so is the value of sys.stdout essentially copied to a local variable of the same name? – Cat Apr 12 '18 at 02:01
  • 1
    @Cat Exactly. Well, global, not local, but close enough. And also, it's a bit confusing to talk about "copying" like that. There's a value—a file object somewhere in memory, and there's a variable, `stdout`, in the `sys` module's globals that refers to that value. The value never gets copied, but in a sense the reference to that value does get copied into the name `stdout` in your module's global namespace. People use terminology like `Y is bound to X` to avoid confusion—but of course it just makes things more confusing until you learn that jargon… – abarnert Apr 12 '18 at 02:09
  • @Cat If you're coming from a language like C, where variables have a memory location, type, and identity, while values are just patterns of bits within a variable. Python isn't like that. Python _values_ have a memory location, type, and identity, and its variables are just names for values stored in a namespace like a module or a class instance. (And a namespace is basically just a dict that you don't have to see directly if you don't want to.) – abarnert Apr 12 '18 at 02:11
  • I see, I think I get it now. Thanks for the helpful answer! – Cat Apr 12 '18 at 02:12
1

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.

viraptor
  • 33,322
  • 10
  • 107
  • 191
1

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    
0

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!

  • 1
    This isn't quite right—it's `globals()['sys'].__dict__['STDOUT']`. Of course the relevant effects would be the same if modules were just dicts rather than objects that have dicts, so the explanation still works, but it would be an even better illustration if it exactly matched what you could see by playing around in the interactive interpreter, rather than raising an `AttributeError`.. – abarnert Apr 12 '18 at 16:53
  • 1
    Also, I know the OP used `STDOUT` in the text of his question, but it would be better to correct that and use the actual name `stdout` than to demonstrate with the non-existent name `STDOUT`. – abarnert Apr 12 '18 at 16:54
  • agreed :) I'll make the relevant changes. You know, in case some stumbles on here and just picks a random answer. – snakes_on_a_keyboard Apr 12 '18 at 17:09