Часто мне нужно вывести данные либо в файл, либо, если файл не указан, в стандартный вывод. Я использую следующий фрагмент:
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 в файл на Python?
- Обработка исключений - интересная статья об обработке исключений в Python по сравнению с C ++
редактировать
Я знаю, что могу обернуть target
, определить отдельную функцию или использовать диспетчер контекста . Я ищу простое, элегантное, идиоматическое решение, для которого не потребуется более 5 строк.
Ответы:
Просто мыслите нестандартно, как насчет специального
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()
Используйте это так:
# For Python 2 you need this line from __future__ import print_function # writes to some_file with smart_open('some_file') as fh: print('some output', file=fh) # writes to stdout with smart_open() as fh: print('some output', file=fh) # writes to stdout with smart_open('-') as fh: print('some output', file=fh)
источник
Придерживайтесь своего текущего кода. Это просто, и вы можете точно сказать , что он делает, просто взглянув на него.
Другой способ - встроенный
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)
источник
with unclosable(sys.stdout): ...
установивsys.stdout.close = lambda: None
внутри этого диспетчера контекста и затем сбросив его на старое значение. Но это кажется слишком надуманным ...sys.stdout
незащищенным, просто отмечая, что это можно сделать. Лучше показать плохие идеи и объяснить, почему они плохие, чем не упоминать их и надеяться, что на них не наткнутся другие.Почему LBYL, если можно EAFP?
try: with open(target, 'w') as h: h.write(content) except TypeError: sys.stdout.write(content)
Зачем переписывать его для единообразного использования блока
with
/,as
если вам нужно заставить его работать запутанным образом? Вы добавите больше строк и снизите производительность.источник
for
цикл Python завершается перехватом ошибки StopIteration, вызванной итератором, через который он проходит, я бы сказал, что использование исключений для управления потоком - это исключительно Pythonic.target
этоNone
когда предназначен sys.stdout, вам нужно поймать,TypeError
а неIOError
.Другое возможное решение: не пытайтесь избегать метода выхода из контекстного менеджера, просто продублируйте стандартный вывод.
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w') if target == '-' else open(target, 'w')) as f: f.write("Foo")
источник
Улучшение ответа Вольфа
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
Это позволяет выполнять двоичный ввод-вывод и передавать возможные посторонние аргументы в
open
еслиfilename
действительно имя файла.источник
Я бы также выбрал простую функцию-оболочку, которая может быть довольно простой, если вы можете игнорировать режим (и, следовательно, 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
источник
ValueError: I/O operation on closed file
если я попытаюсь записать в файл внеwith open_or_stdout(..)
блока. Что мне не хватает? sys.stdout не предназначен для закрытия.Хорошо, если мы вступаем в однострочные войны, вот:
(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)
источник
__del__
будут делать это.Если вы действительно должны настоять на чем-то более "элегантном", например, на однострочном:
>>> 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
.источник
Как насчет открытия нового fd для sys.stdout? Таким образом, у вас не возникнет проблем с его закрытием:
if not target: target = "/dev/stdout" with open(target, 'w') as f: f.write(content)
источник
./script.py >> file
перезаписывать файл вместо добавления к нему.if (out != sys.stdout): with open(out, 'wb') as f: f.write(data) else: out.write(data)
В некоторых случаях небольшое улучшение.
источник
import contextlib import sys with contextlib.ExitStack() as stack: h = stack.enter_context(open(target, 'w')) if target else sys.stdout h.write(content)
Всего две дополнительные строки, если вы используете Python 3.3 или выше: одна строка для дополнительных
import
и одна строка дляstack.enter_context
.источник
Если это нормально, что
sys.stdout
закрыто послеwith
тела, вы также можете использовать такие шаблоны:# Use stdout when target is "-" with open(target, "w") if target != "-" else sys.stdout as f: f.write("hello world") # Use stdout when target is falsy (None, empty string, ...) with open(target, "w") if target else sys.stdout as f: f.write("hello world")
или даже в более общем плане:
with target if isinstance(target, io.IOBase) else open(target, "w") as f: f.write("hello world")
источник