`with open (…)`과`sys.stdout`을 모두 멋지게 처리하는 방법은 무엇입니까?


92

종종 파일 또는 파일이 지정되지 않은 경우 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')
...

원래 stdout을 복원하는 것을 기억해야하기 때문입니다.

관련 :

편집하다

랩핑 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)

28

현재 코드를 고수하십시오. 간단하고 흘끗 쳐다 보는 것만으로 정확히 무엇을하는지 알 수 있습니다 .

또 다른 방법은 인라인을 사용하는 것입니다 if.

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

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

그러나 그것은 당신이 가진 것보다 훨씬 짧지 않으며 틀림없이 더 나빠 보입니다.

sys.stdout닫을 수 없게 만들 수도 있지만 너무 파이썬 적으로 보이지는 않습니다.

sys.stdout.close = lambda: None

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

2
컨텍스트 관리자를 만들어 필요한만큼 닫을 수 있습니다. 이 컨텍스트 관리자 내부 with unclosable(sys.stdout): ...에 설정 sys.stdout.close = lambda: None하고 나중에 이전 값으로 재설정하면됩니다. 그러나 이것은 조금 너무 멀리 가져온 것 같습니다 ...
glglgl

3
나는 "그만두세요, 당신은 그것이 무엇을하고 있는지 정확히 알 수 있습니다"에 대한 투표와 끔찍한 닫을 수없는 제안에 대해 투표하는 것 사이에서 갈등이 있습니다!
GreenAsJade

@GreenAsJade 나는 그가 닫을 수 없게 만드는 것을 제안 했다고 생각하지 않습니다 sys.stdout. 나쁜 아이디어를 보여주고 왜 나쁜지 설명하는 것이 낫습니다. 다른 사람들에게 우연히 마주 치지 않기를 바랍니다.
cjs

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

31
@JakubM. 예외는 파이썬에서 이와 같이 사용할 수 있고, 사용해야하며, 사용할 수 있습니다.
Gareth Latty 2013

13
Python의 for루프가 반복되는 반복자에 의해 발생한 StopIteration 오류를 포착하여 종료 된다는 점을 고려할 때 흐름 제어를 위해 예외를 사용하는 것은 완전히 Pythonic이라고 말하고 싶습니다.
Kirk Strauser 2013

1
그 가정 target입니다 Nonesys.stdout이하고자하는 경우, 당신이 잡을 필요 TypeError보다는 IOError.
torek

5

또 다른 가능한 해결책 : 컨텍스트 관리자 종료 방법을 피하지 말고 stdout을 복제하십시오.

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

5

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

이것은 바이너리 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

1
블록 ValueError: I/O operation on closed file외부의 파일에 쓰려고하면 얻을 수 with open_or_stdout(..)있습니다. 내가 무엇을 놓치고 있습니까? sys.stdout은 닫히지 않습니다.
Tommi Komulainen

1

좋아, 우리가 한 줄로 전쟁을 시작한다면, 여기에 :

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

나는 맥락이 한곳에서만 쓰여지는 한 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는 0이되므로 __del__ 메서드를 즉시 (cpython에서) 호출하거나 나중에 가비지 수집이 발생할 때 호출 할 수 있습니다. 문서에는 이것이 항상 작동한다고 믿지 않는다는 경고가 있지만 짧은 스크립트에서 항상 사용합니다. 오래 실행되고 많은 파일을 여는 큰 것 ... 글쎄, 나는 'with'또는 'try / finally'를 사용할 것 같습니다.
tdelaney

TIL. 나는 파일 객체 __del__가 그렇게 할 줄 몰랐다 .
2rs2ts

@ 2rs2ts : CPython은 참조 계산 가비지 수집기 (필요에 따라 "실제"GC가 호출 됨)를 사용하므로 스트림 핸들에 대한 모든 참조를 삭제하자마자 파일을 닫을 수 있습니다. 자이 썬과 분명히 IronPython은 "실제"GC 만 가지고 있으므로 최종 GC가 될 때까지 파일을 닫지 않습니다.
torek

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
황제를

0

sys.stdout에 대한 새 fd를 여는 것은 어떻습니까? 이렇게하면 닫는 데 문제가 없습니다.

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

1
슬프게도이 파이썬 스크립트를 실행하려면 설치시 sudo가 필요합니다. / dev / stdout은 루트 소유입니다.
Manur 2015

많은 상황에서 fd를 stdout으로 다시 여는 것은 예상 한 것이 아닙니다. 예를 들어,이 코드는 stdout을 잘라서 파일에 추가하는 대신 ./script.py >> file 덮어 쓰는 것과 같은 쉘을 만듭니다 .
salicideblock

/ dev / stdout이없는 창에서는 작동하지 않습니다.
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 이상을 사용하는 경우 두 줄만 추가하면됩니다. 한 줄은 추가로 import, 한 줄은 stack.enter_context.


0

시체를 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")
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.