Как обращаться с `` с открытым (...) `и` sys.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.stdout закрывается при выходе из блока with
, и я этого не хочу. Я не хочу
stdout = open(target, 'w')
...
потому что мне нужно будет помнить, что нужно восстановить исходный stdout.
Связанный:
Edit
Я знаю, что могу обернуть target
, определить отдельную функцию или использовать контекстный менеджер. Я ищу простой, элегантный, идиоматический подход к решению, который не требует больше 5 строк
Ответы
Ответ 1
Просто думая вне поля здесь, как насчет пользовательского метода 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'
Ответ 2
Вставьте свой текущий код. Это просто, и вы можете точно сказать, что он делает, просто взглянув на него.
Другой способ - с встроенным if
:
handle = open(target, 'w') if target else sys.stdout
handle.write(content)
if handle is not sys.stdout:
handle.close()
Но это не намного короче, чем у вас, и это выглядит хуже.
Вы также можете сделать sys.stdout
незамкнутым, но это не похоже на Pythonic:
sys.stdout.close = lambda: None
with (open(target, 'w') if target else sys.stdout) as handle:
handle.write(content)
Ответ 3
Почему LBYL, когда вы можете использовать EAFP?
try:
with open(target, 'w') as h:
h.write(content)
except TypeError:
sys.stdout.write(content)
Зачем переписывать его, чтобы использовать блок with
/as
равномерно, когда вы должны заставить его работать запутанно? Вы добавите больше строк и уменьшите производительность.
Ответ 4
Улучшение ответа Вольфа
import sys
import contextlib
@contextlib.contextmanager
def smart_open(filename: str, mode: str = 'r', *args, **kwargs):
'''Open files and i/o streams transparently.'''
if filename == '-':
if 'r' in mode:
stream = sys.stdin
else:
stream = sys.stdout
if 'b' in mode:
fh = stream.buffer # type: IO
else:
fh = stream
close = False
else:
fh = open(filename, mode, *args, **kwargs)
close = True
try:
yield fh
finally:
if close:
try:
fh.close()
except AttributeError:
pass
Это позволяет бинарный IO и передать возможные посторонние аргументов, чтобы open
, если filename
действительно имя файла.
Ответ 5
Другое возможное решение: не пытайтесь избежать метода выхода из контекстного менеджера, просто дублируйте stdout.
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w')
if target == '-'
else open(target, 'w')) as f:
f.write("Foo")
Ответ 6
Я также хотел бы использовать простую функцию-обертку, которая может быть довольно простой, если вы можете игнорировать режим (и, следовательно, stdin vs. 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
Ответ 7
Хорошо, если мы попадаем в однолинейные войны, вот что:
(target and open(target, 'w') or sys.stdout).write(content)
Мне нравится оригинальный пример Джейкоба, если контекст написан только в одном месте. Было бы проблемой, если вы в конечном итоге повторно открыли файл для многих записей. Думаю, я бы сразу принял решение в верхней части script и давал системе закрыть файл при выходе:
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)
Ответ 8
Как насчет открытия нового fd для sys.stdout? Таким образом у вас не будет никаких проблем с его закрытием:
if not target:
target = "/dev/stdout"
with open(target, 'w') as f:
f.write(content)
Ответ 9
Если вы действительно должны настаивать на чем-то более "элегантном", то есть на одном слое:
>>> 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
.
Ответ 10
if (out != sys.stdout):
with open(out, 'wb') as f:
f.write(data)
else:
out.write(data)
Небольшое улучшение в некоторых случаях.