Как обрабатывать оба 'с открытым (...) `и' sys.поток stdout` красиво?
часто мне нужно выводить данные либо в файл, либо, если файл не указан, в stdout. Я использую следующий фрагмент:
if target:
with open(target, 'w') as h:
h.write(content)
else:
sys.stdout.write(content)
Я хотел бы переписать его и обрабатывать обе цели равномерно.
в идеале это будет:
with open(target, 'w') as h:
h.write(content)
но это не будет работать хорошо, потому что sys.стандартный вывод будет закрыт при выходе with
заблокировать и я не хочу этого. Я тоже не хочу
stdout = open(target, 'w')
...
потому что мне нужно будет помнить, чтобы восстановить оригинал стандартный вывод.
по теме:
- перенаправить stdout в файл на Python?
- Обработка Исключений - интересная статья об обработке исключений в Python, по сравнению с C++
Edit
Я знаю, что могу обернуть target
, определить отдельную функцию или использовать контекст менеджер. Я ищу простое, элегантное, идиоматическое решение, подходящее для этого не потребуется более 5 строк
10 ответов:
просто думая вне коробки здесь, как насчет обычая
open()
способ?import sys import contextlib @contextlib.contextmanager def smart_open(filename=None): if filename and filename != '-': fh = open(filename, 'w') else: fh = sys.stdout try: yield fh finally: if fh is not sys.stdout: fh.close()
используйте его так:
# writes to some_file with smart_open('some_file') as fh: print >>fh, 'some output' # writes to stdout with smart_open() as fh: print >>fh, 'some output' # writes to stdout with smart_open('-') as fh: print >>fh, 'some output'
придерживайтесь текущего кода. Это просто, и вы можете сказать ровно что он делает, просто взглянув на него.
другой способ был бы со встроенным
if
:handle = open(target, 'w') if target else sys.stdout handle.write(content) if handle is not sys.stdout: handle.close()
но это не намного короче, чем то, что у вас есть, и это выглядит, возможно, хуже.
вы также можете сделать
sys.stdout
unclosable, но это не слишком подходящие для Python:sys.stdout.close = lambda: None with (open(target, 'w') if target else sys.stdout) as handle: handle.write(content)
почему LBYL, когда вы можете EAFP?
try: with open(target, 'w') as h: h.write(content) except TypeError: sys.stdout.write(content)
зачем переписывать его, чтобы использовать
with
/as
блок равномерно, когда вы должны заставить его работать в извилистый путь? Вы добавите больше линии и снизить производительность.
Я бы также пошел на простую функцию обертки, которая может быть довольно простой, если вы можете игнорировать режим (и, следовательно, stdin против stdout), например:
from contextlib import contextmanager import sys @contextmanager def open_or_stdout(filename): if filename != '-': with open(filename, 'w') as f: yield f else: yield sys.stdout
другое возможное решение: не пытайтесь избежать метода выхода context manager, просто дублируйте stdout.
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w') if target == '-' else open(target, 'w')) as f: f.write("Foo")
улучшение Wolph это!--5-->
import sys import contextlib @contextlib.contextmanager def smart_open(filename: str = None, mode: str = 'r', *args, **kwargs): if filename == '-': if 'r' in mode: stream = sys.stdin else: stream = sys.stdout if 'b' in mode: fh = stream.buffer else: fh = stream else: fh = open(filename, mode, *args, **kwargs) try: yield fh finally: try: fh.close() except AttributeError: pass
Это позволяет двоичный IO и передать возможные посторонние аргументы в
open
еслиfilename
- это действительно имя файла.
хорошо, если мы вступаем в однострочные войны, вот:
(target and open(target, 'w') or sys.stdout).write(content)
мне нравится оригинальный пример Джейкоба, пока контекст написан только в одном месте. Это было бы проблемой, если вы в конечном итоге повторно открыть файл для многих записей. Я думаю, что я бы просто принял решение один раз в верхней части скрипта и позволил системе закрыть файл при выходе:
output = target and open(target, 'w') or sys.stdout ... output.write('thing one\n') ... output.write('thing two\n')
вы можете включить свой собственный обработчик, если вы считаете его более опрятным
import atexit def cleanup_output(): global output if output is not sys.stdout: output.close() atexit(cleanup_output)
Как насчет открытия нового fd для sys.stdout? Таким образом, у вас не будет никаких проблем с его закрытием:
if not target: target = "/dev/stdout" with open(target, 'w') as f: f.write(content)
Если вы действительно должны настаивать на чем-то более "элегантное", т. е. одну строчку:
>>> import sys >>> target = "foo.txt" >>> content = "foo" >>> (lambda target, content: (lambda target, content: filter(lambda h: not h.write(content), (target,))[0].close())(open(target, 'w'), content) if target else sys.stdout.write(content))(target, content)
foo.txt
появляется и содержит текстfoo
.