`open(…)`と `sys.stdout`の両方をうまく処理する方法は?


91

多くの場合、ファイルまたはファイルが指定されていない場合は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)

しかし、withブロックを離れるときにsys.stdoutが閉じられているので、これはうまくいきません。私もしたくない

stdout = open(target, 'w')
...

元の標準出力を復元することを覚えておく必要があるからです。

関連:

編集する

ラップしたりtarget、別の関数を定義したり、コンテキストマネージャを使用したりできることはわかっています。5行以上必要としないシンプルでエレガントな慣用的なソリューションフィッティングを探しています


先ほど編集を追加しなかったのは残念です;)とにかく...代わりに、開いているファイルをクリーンアップする手間が
省け

回答:


92

ここでボックスの外側を考えるだけで、カスタム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)

27

現在のコードを使用してください。シンプルで、一目見ただけで何をしているのか正確にわかります。

別の方法は、インラインを使用することifです:

handle = open(target, 'w') if target else sys.stdout
handle.write(content)

if handle is not sys.stdout:
    handle.close()

しかし、それはあなたが持っているものよりもはるかに短いわけではなく、間違いなく悪く見えます。

sys.stdoutクローズ可能にすることもできますが、Pythonのようには見えません。

sys.stdout.close = lambda: None

with (open(target, 'w') if target else sys.stdout) as handle:
    handle.write(content)

1
コンテキストマネージャーを作成することで、必要な限り非公開性を保つことができます。このコンテキストマネージャー内にwith unclosable(sys.stdout): ...設定sys.stdout.close = lambda: Noneし、後で古い値にリセットします。しかし、これは少し遠すぎるようです...
glglgl 2013

3
私は「去る、あなたはそれが何をしているのか正確に言うことができる」という投票と、恐ろしい閉鎖できない提案のために投票することの間で引き裂かれています!
GreenAsJade 2016

8

EAFPが可能なのになぜLBYLなのか?

try:
    with open(target, 'w') as h:
        h.write(content)
except TypeError:
    sys.stdout.write(content)

複雑な方法で機能させる必要があるのに、with/ asブロックを均一に使用するように書き換えるのはなぜですか?行を追加しパフォーマンスを低下させます。


3
例外使用してルーチンの「通常の」フローを制御することはできません。パフォーマンス?エラーの泡立ちはif / elseよりも速くなりますか?
Jakub M.

2
どちらかを使用する確率に依存します。
2rs2ts 2013

31
@JakubM。例外は、Pythonでこのように使用できます。
Gareth Latty 2013

13
Pythonのforループは、ループするイテレーターによってスローされたStopIterationエラーをキャッチすることで終了することを考えると、フロー制御に例外を使用することは完全にPythonicだと思います。
Kirk Strauser 2013

1
それtargetNonesys.stdoutが意図されているときであると仮定すると、でTypeErrorなくキャッチする必要がありますIOError
2013

5

別の考えられる解決策:コンテキストマネージャーの終了メソッドを回避しようとせず、stdoutを複製してください。

with (os.fdopen(os.dup(sys.stdout.fileno()), 'w')
      if target == '-'
      else open(target, 'w')) as f:
      f.write("Foo")

5

ウルフの答えの改善

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が可能になり、最終的に無関係な引数がopenif filenameが実際にファイル名である場合に渡されます。


1

また、単純なラッパー関数も使用します。これは、モード(およびその結果として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

このソリューションは、with句の正常終了時またはエラー終了時にファイルを明示的に閉じないため、コンテキストマネージャーはそれほど多くありません。開始終了を実装するクラスの方が適しています。
tdelaney 2013

1
私が取得ValueError: I/O operation on closed file私は外のファイルへの書き込みをしようとするとwith open_or_stdout(..)ブロック。何が欠けていますか?sys.stdoutは閉じられることを意図していません。
Tommi Komulainen 2013

1

さて、私たちがワンライナー戦争に入るなら、ここにあります:

(target and open(target, 'w') or sys.stdout).write(content)

コンテキストが1か所にのみ記述されている限り、Jacobの元の例が好きです。何度も書き込みを行うためにファイルを再度開いてしまうと、問題になります。スクリプトの先頭で一度決定して、終了時にシステムにファイルを閉じさせるだけだと思います。

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)

私はあなたのワンライナーがファイルオブジェクトを閉じるとは思わない。私が間違っている?
2rs2ts 2013

1
@ 2rs2ts-条件付きで実行されます。ファイルオブジェクトを参照する変数がないため、ファイルオブジェクトのrefcountはゼロになります。そのため、ガベージコレクションが発生した直後(cpythonの場合)または後で__del__メソッドを呼び出すことができます。これが常に機能することを信頼しないというドキュメント内の警告がありますが、短いスクリプトでは常にそれを使用しています。長時間実行し、多くのファイルを開く大きなもの...私は 'with'または 'try / finally'を使用すると思います。
tdelaney 2013

TIL。ファイルオブジェクトがそれを実行することを知りませんでし__del__た。
2rs2ts 2013

@ 2rs2ts:CPythonは参照カウントガベージコレクター(必要に応じて呼び出される「実際の」GCを使用)を使用するため、ストリームハンドルへのすべての参照をドロップするとすぐにファイルを閉じることができます。JythonとどうやらIronPythonには「実際の」GCしかないので、最終的にGCされるまでファイルを閉じません。
2013

0

もっと「エレガント」なもの、つまりワンライナーを本当に主張しなければならない場合:

>>> 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


これは、CodeGolf StackExchange:D
kaiser

0

sys.stdoutの新しいfdを開くのはどうですか?このようにして、それを閉じるときに問題が発生しません。

if not target:
    target = "/dev/stdout"
with open(target, 'w') as f:
    f.write(content)

1
残念ながら、このpythonスクリプトを実行するには、インストール時にsudoが必要です。/ dev / stdoutはrootが所有しています。
Manur 2015年

多くの場合、fdをstdoutに再度開くことは、予期されていることではありません。たとえば、このコードはstdoutを切り捨てるため、ファイルに追加するのではなく、ファイルを./script.py >> file 上書きするなどのシェル処理を行います。
salicideblock 2018年

これは、/ dev / stdoutがないWindowsでは機能しません。
Bryan Oakley

0
if (out != sys.stdout):
    with open(out, 'wb') as f:
        f.write(data)
else:
    out.write(data)

いくつかのケースでわずかな改善。


0
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以降を使用している場合は、2行追加します。1行は追加用import、もう1行は用stack.enter_contextです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.