Seringkali saya perlu mengeluarkan data ke file atau, jika file tidak ditentukan, ke stdout. Saya menggunakan potongan berikut:
if target:
with open(target, 'w') as h:
h.write(content)
else:
sys.stdout.write(content)
Saya ingin menulis ulang dan menangani kedua target secara seragam.
Dalam kasus yang ideal itu adalah:
with open(target, 'w') as h:
h.write(content)
tetapi ini tidak akan berfungsi dengan baik karena sys.stdout ditutup saat keluar dari with
blok dan saya tidak menginginkannya. Saya juga tidak mau
stdout = open(target, 'w')
...
karena saya harus ingat untuk mengembalikan stdout asli.
Terkait:
- Alihkan stdout ke file dengan Python?
- Menangani Pengecualian - artikel menarik tentang menangani pengecualian dengan Python, dibandingkan dengan C ++
Edit
Saya tahu bahwa saya dapat membungkus target
, menentukan fungsi terpisah atau menggunakan manajer konteks . Saya mencari solusi yang sederhana, elegan, dan idiomatis yang tidak memerlukan lebih dari 5 baris
Jawaban:
Hanya berpikir di luar kebiasaan di sini, bagaimana dengan
open()
metode khusus ?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()
Gunakan seperti ini:
# 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)
sumber
Tetap gunakan kode Anda saat ini. Sederhana dan Anda dapat mengetahui dengan tepat apa yang dilakukannya hanya dengan melihatnya sekilas.
Cara lain adalah dengan inline
if
:handle = open(target, 'w') if target else sys.stdout handle.write(content) if handle is not sys.stdout: handle.close()
Tapi itu tidak jauh lebih pendek dari yang Anda miliki dan terlihat lebih buruk.
Anda juga bisa membuat
sys.stdout
unclosable, tapi itu sepertinya tidak terlalu Pythonic:sys.stdout.close = lambda: None with (open(target, 'w') if target else sys.stdout) as handle: handle.write(content)
sumber
with unclosable(sys.stdout): ...
dengan menyetelsys.stdout.close = lambda: None
di dalam pengelola konteks ini dan menyetel ulang ke nilai lama sesudahnya. Tapi ini sepertinya terlalu dibuat-buat ...sys.stdout
ditutup, hanya mencatat bahwa itu bisa dilakukan. Lebih baik menunjukkan ide-ide buruk dan menjelaskan mengapa ide-ide itu buruk daripada tidak menyebutkannya dan berharap ide-ide itu tidak tersandung oleh orang lain.Mengapa LBYL saat Anda bisa EAFP?
try: with open(target, 'w') as h: h.write(content) except TypeError: sys.stdout.write(content)
Mengapa menulis ulang untuk menggunakan blok
with
/as
secara seragam ketika Anda harus membuatnya bekerja dengan cara yang berbelit-belit? Anda akan menambahkan lebih banyak garis dan mengurangi kinerja.sumber
for
loop Python keluar dengan menangkap kesalahan StopIteration yang dilemparkan oleh iterator yang dilewatinya, saya akan mengatakan bahwa menggunakan pengecualian untuk kontrol aliran sepenuhnya Pythonic.target
adalahNone
ketika sys.stdout dimaksudkan, Anda perlu untuk menangkapTypeError
bukanIOError
.Solusi lain yang mungkin: jangan mencoba untuk menghindari metode keluar dari manajer konteks, cukup duplikat stdout.
with (os.fdopen(os.dup(sys.stdout.fileno()), 'w') if target == '-' else open(target, 'w')) as f: f.write("Foo")
sumber
Peningkatan dari jawaban Wolph
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
Hal ini memungkinkan IO biner dan meneruskan argumen asing yang akhirnya
open
jikafilename
memang berupa nama file.sumber
Saya juga akan menggunakan fungsi pembungkus sederhana, yang bisa sangat sederhana jika Anda dapat mengabaikan mode (dan akibatnya stdin vs. stdout), misalnya:
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
sumber
ValueError: I/O operation on closed file
jika saya mencoba menulis ke file di luarwith open_or_stdout(..)
blok. Apa yang saya lewatkan? sys.stdout tidak dimaksudkan untuk ditutup.Oke, jika kita terlibat dalam perang satu baris, berikut ini:
(target and open(target, 'w') or sys.stdout).write(content)
Saya suka contoh asli Yakub asalkan konteksnya hanya ditulis di satu tempat. Ini akan menjadi masalah jika Anda akhirnya membuka kembali file untuk banyak penulisan. Saya pikir saya hanya akan membuat keputusan sekali di bagian atas skrip dan membiarkan sistem menutup file saat keluar:
output = target and open(target, 'w') or sys.stdout ... output.write('thing one\n') ... output.write('thing two\n')
Anda dapat memasukkan penangan keluar Anda sendiri jika menurut Anda itu lebih rapi
import atexit def cleanup_output(): global output if output is not sys.stdout: output.close() atexit(cleanup_output)
sumber
__del__
akan melakukan itu.Jika Anda benar-benar harus menuntut sesuatu yang lebih "elegan", yaitu satu kalimat:
>>> 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
muncul dan berisi teksfoo
.sumber
Bagaimana jika membuka fd baru untuk sys.stdout? Dengan cara ini Anda tidak akan kesulitan menutupnya:
if not target: target = "/dev/stdout" with open(target, 'w') as f: f.write(content)
sumber
./script.py >> file
menimpa file daripada menambahkannya.if (out != sys.stdout): with open(out, 'wb') as f: f.write(data) else: out.write(data)
Sedikit peningkatan dalam beberapa kasus.
sumber
import contextlib import sys with contextlib.ExitStack() as stack: h = stack.enter_context(open(target, 'w')) if target else sys.stdout h.write(content)
Hanya dua baris tambahan jika Anda menggunakan Python 3.3 atau lebih tinggi: satu baris untuk ekstra
import
dan satu baris untukstack.enter_context
.sumber
Jika fine yang
sys.stdout
ditutup afterwith
body, Anda juga bisa menggunakan pola seperti ini:# 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")
atau bahkan lebih umum:
with target if isinstance(target, io.IOBase) else open(target, "w") as f: f.write("hello world")
sumber