programing

"with open(...)"과 "sys.stdout" 둘 다 잘 다루는 방법은?

sourcejob 2023. 8. 22. 22:06
반응형

"with open(...)"과 "sys.stdout" 둘 다 잘 다루는 방법은?

종종 데이터를 파일로 출력하거나 파일이 지정되지 않은 경우 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을 복구하는 것을 기억해야 하기 때문입니다.

관련:

편집

제가 포장할 수 있다는 것을 알고 있습니다.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닫을 수는 없지만, 그것은 너무 파이썬적으로 보이지는 않습니다.

sys.stdout.close = lambda: None

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

울프의 답변 개선

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파일 이름입니다.

당신이 EAFP를 할 수 있는데 왜 LBIL인가요?

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

사용하기 위해 다시 쓰는 이유는 무엇입니까?with/as복잡한 방식으로 작동해야 할 때 일률적으로 차단합니까?줄을 더 추가하고 성능을 줄일 수 있습니다.

Python의 조건문에서 지적했듯이, Python 3.7은 contextlib 사용을 허용합니다.다음에 대한 null 컨텍스트:

from contextlib import nullcontext

with open(target, "w") if target else nullcontext(sys.stdout) as f:
    f.write(content)

다른 가능한 해결책은 컨텍스트 관리자 종료 방법을 피하려고 하지 말고 stdout을 복제하는 것입니다.

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

괜찮으시다면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")
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.

또한 간단한 래퍼 함수도 사용할 수 있는데, 모드(그리고 결과적으로 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

좋아요, 만약 우리가 한 줄기 전쟁을 한다면, 다음과 같습니다.

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

이것은 승인된 답변의 간단하고 짧은 버전입니다.

import contextlib, sys


def writer(fn): 
    @contextlib.contextmanager
    def stdout():
        yield sys.stdout
    return open(fn, 'w') if fn else stdout()

용도:

with writer('') as w:
    w.write('hello\n')

with writer('file.txt') as w:
    w.write('hello\n')

만약 당신이 정말로 더 "우아한" 것, 즉 원라이너를 고집해야 한다면:

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

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

if not target:
    target = "/dev/stdout"
with open(target, 'w') as f:
    f.write(content)
if (out != sys.stdout):
    with open(out, 'wb') as f:
        f.write(data)
else:
    out.write(data)

일부 경우에는 약간의 개선이 있습니다.

다음 해결책은 아름다움이 아니라 아주 오래 전의 것입니다. 바로 그 전에...

handler = open(path, mode = 'a') if path else sys.stdout
try:
    print('stuff', file = handler)
    ... # other stuff or more writes/prints, etc.
except Exception as e:
    if not (path is None): handler.close()
    raise e
handler.close()

그것을 해결하는 한 가지 방법은 다형성입니다.Pathlib.path을 가지고 있습니다.open예상대로 작동하는 방법:

from pathlib import Path

output = Path("/path/to/file.csv")

with output.open(mode="w", encoding="utf-8") as f:
    print("hello world", file=f)

인쇄를 위해 이 인터페이스를 복사할 수 있습니다.

import sys

class Stdout:
    def __init__(self, *args):
        pass

    def open(self, mode=None, encoding=None):
        return self

    def __enter__(self):
        return sys.stdout

    def __exit__(self, exc_type, exc_value, traceback):
        pass

이제 우리는 간단히 교체합니다.Path와 함께Stdout

output = Stdout("/path/to/file.csv")

with output.open(mode="w", encoding="utf-8") as f:
    print("hello world", file=f)

것은 .open하지만 당신이 사용하고 있다면 편리한 해결책입니다.Path물건들.

을 사용할 수 .stdout 파일 않도록 로 남겨 .closefd=False:

h = open(target, 'w') if target else open(sys.stdout.fileno(), 'w', closefd=False)

with h as h:
    h.write(content)

언급URL : https://stackoverflow.com/questions/17602878/how-to-handle-both-with-open-and-sys-stdout-nicely

반응형