ThorSummoner
ThorSummoner

Reputation: 18159

Python context for file or None

Python is going to call a subprocess, the user either requested that subprocesses stdout is to go to a file (or back-holed to os.devnull), or the subprocesses output is to be passed though "in real time".

My current best guess as how to do this would seemingly work:


with open(file_path, 'wb') if logging else None as shell_stdout:
    subprocess.call(['ls'], stdout=shell_stdout)

In tinkering/testing this appears to be the right value, which I have assumed work well with subprocess.call. However, and unsurprisingly I get the following exception:

AttributeError: __exit__

So None is not a context, it has no __exit__;


Goals

So, how could this behavior be achieved? Or what would you suggest doing instead/alternatively?

Upvotes: 4

Views: 1101

Answers (3)

Mad Physicist
Mad Physicist

Reputation: 114440

As of Python 3.3, tou can define a no-op context manager using contextlib.ExitStack:

from contextlib import ExitStack

with open(...) if ... else ExitStack():
    ...

Upvotes: 0

unutbu
unutbu

Reputation: 880399

You could create a "no-op" context manager:

import subprocess
import contextlib
@contextlib.contextmanager
def noop():
    yield None

logging = False
file_path = '/tmp/out'

with open(file_path, 'wb') if logging else noop() as shell_stdout:
    subprocess.call(['ls'], stdout=shell_stdout)

When logging is True, the conditional expression returns a file object. When logging is False, it returns a noop() context manager (so it can be used in the with-statement), which sets shell_out to None but doesn't do anything special upon exit.


Per the docs, when stdout=None,

... no redirection will occur; the child’s file handles will be inherited from the parent.

Usually the parent's stdout would equal sys.stdout. However, it is possible to redirect sys.stdout somewhere else, either explicitly (e.g. sys.stdout = open('/tmp/stdout', 'wb')) or indirectly, such as by using module that redirects sys.stdout. The fileinput module from the standard library redirects sys.stdout, for example. In such cases noop() might be useful for directing stdout to the parent's stdout, which may be different than sys.stdout.

If this corner case does not affect you, then Padraic Cunningham's solution is simpler:

with open(file_path, 'wb') if logging else sys.stdout as shell_stdout:
    subprocess.call(['ls'], stdout=shell_stdout)

Upvotes: 7

Padraic Cunningham
Padraic Cunningham

Reputation: 180481

Either set shell_stdout to stdout or a file object based on logging being True or False, there is no need to overcomplicate it, you only have one condition or the other if logging will either be True or False, there is nothing wrong with opening a file not using with, there are times when using with does not fit.

import sys

if logging:
      shell_stdout = open(file_path, 'wb') # to file or devnull if logging
else:
    shell_stdout = sys.stdout
subprocess.call(['ls'], stdout=shell_stdout) # real time if not logging
shell_stdout.close()

To do what you wanted in your question you can do the following, you don't need anything bar sys.stdout:

with open(file_path, 'wb') if logging else sys.stdout as shell_stdout:
    subprocess.call(['ls'], stdout=shell_stdout)

The file will only be created if logging is True.

Upvotes: 3

Related Questions