Hem "open (…)" hem de "sys.stdout" ile nasıl başa çıkılır?


95

Genellikle ya dosyaya ya da dosya belirtilmemişse stdout'a veri çıkışı yapmam gerekir. Aşağıdaki pasajı kullanıyorum:

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

Yeniden yazmak ve her iki hedefi de aynı şekilde ele almak istiyorum.

İdeal durumda şöyle olur:

with open(target, 'w') as h:
    h.write(content)

ancak bu iyi çalışmayacak çünkü withbloktan çıkarken sys.stdout kapatılacak ve ben bunu istemiyorum. Ben de istemiyorum

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

çünkü orijinal stdout'u geri yüklemeyi hatırlamam gerekiyor.

İlişkili:

Düzenle

Sarmalayabileceğimi target, ayrı işlev tanımlayabileceğimi veya bağlam yöneticisi kullanabileceğimi biliyorum . 5 satırdan fazlasını gerektirmeyen basit, zarif, deyimsel bir çözüm arıyorum


Düzenlemeyi daha önce eklememeniz çok kötü;) Her neyse ... alternatif olarak açık dosyanızı temizleme zahmetine
giremezsiniz

Yanıtlar:


94

Burada kutunun dışında düşünmek, bir gelenek open() yönteme ?

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

Bunu şu şekilde kullanın:

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

31

Mevcut kodunuza bağlı kalın. Çok basit ve tam olarak söyleyebilirsin ne yaptığını sadece ona bakarak .

Başka bir yol da satır içi olabilir if:

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

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

Ancak bu, sahip olduğunuzdan çok daha kısa değil ve tartışmasız daha kötü görünüyor.

Ayrıca sys.stdoutkapatılamaz da yapabilirsiniz , ancak bu pek de Pythonic görünmüyor:

sys.stdout.close = lambda: None

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

2
: Uzun siz de bunun için bir bağlam yöneticisi yaparak ihtiyaç olduğu kadar için unclosability tutabilir with unclosable(sys.stdout): ...ayarlayarak sys.stdout.close = lambda: Nonebu bağlam yöneticisi içinde ve sonrasında eski değere sıfırlamadan. Ama bu biraz fazla zorlanmış görünüyor ...
glglgl

3
"Bırakın, tam olarak ne yaptığını söyleyebilirsiniz" için oy vermekle, kapatılamaz korkunç öneriye oy vermek arasında parçalandım!
GreenAsJade

@GreenAsJade Onun olduğunu sanmıyorum düşündüren yapım sys.stdout, unclosable sadece yapılabilir tht kaydetti. Kötü fikirleri göstermek ve neden kötü olduklarını açıklamak, onlardan bahsetmemek ve başkaları tarafından tökezlenmediklerini ummaktan daha iyidir.
cjs

8

EAFP yapabildiğinizde neden LBYL?

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

Kıvrımlı bir şekilde çalıştırmanız gerektiğinde with/ asbloğu tek tip olarak kullanmak için neden yeniden yazasınız? Daha fazla satır ekleyecek ve performansı düşüreceksiniz.


3
İstisnalar olmamalı "normal" kontrol rutin akmaya kullanılabilir. Verim? bir hatanın köpürtülmesi, if / else'ten daha hızlı olur mu?
Jakub M.

2
Birini veya diğerini kullanma olasılığınıza bağlıdır.
2rs2ts

32
@JakubM. Python'da istisnalar bu şekilde olabilir, olmalıdır ve kullanılmaktadır.
Gareth Latty

13
Python'un fordöngüsünün, döngü yaptığı yineleyici tarafından atılan bir StopIteration hatasını yakalayarak çıktığını düşünürsek, akış kontrolü için istisnalar kullanmanın tamamen Pythonic olduğunu söyleyebilirim.
Kirk Strauser

1
Varsayılırsa targetolduğu Nonesys.stdout düşünülmüşse, sen yakalamak için gereken TypeErrorziyade IOError.
torek

5

Başka bir olası çözüm: bağlam yöneticisi çıkış yönteminden kaçınmaya çalışmayın, sadece stdout'u kopyalayın.

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

5

Wolph'un cevabında bir gelişme

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

Bu, ikili IO'ya izin verir ve openeğer filenamegerçekten bir dosya adı ise , nihai harici argümanları iletir .


1

Ayrıca basit bir sarmalayıcı işlevini tercih ederim, eğer modu (ve dolayısıyla stdin'e karşı stdout'u) görmezden gelirseniz oldukça basit olabilir, örneğin:

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

Bu çözüm, with cümlesinin normal veya hata sonlandırmasında dosyayı açıkça kapatmaz, bu nedenle bir bağlam yöneticisi değildir. Uygular A sınıfı girmek ve çıkmak daha iyi bir seçim olacaktır.
tdelaney

1
Ben olsun ValueError: I/O operation on closed fileben dışarıda dosyaya yazma denerseniz with open_or_stdout(..)bloğun. Neyi kaçırıyorum? sys.stdout'un kapatılması amaçlanmamıştır.
Tommi Komulainen

1

Tamam, tek astarlı savaşlara giriyorsak, işte:

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

Bağlam yalnızca tek bir yerde yazıldığı sürece Jacob'ın orijinal örneğini seviyorum. Dosyayı birçok yazma işlemi için yeniden açmanız sorun olur. Sanırım kararı komut dosyasının en üstüne bir kez verirdim ve sistemin çıkışta dosyayı kapatmasına izin verirdim:

output = target and open(target, 'w') or sys.stdout
...
output.write('thing one\n')
...
output.write('thing two\n')

Daha düzenli olduğunu düşünüyorsanız kendi çıkış işleyicinizi dahil edebilirsiniz.

import atexit

def cleanup_output():
    global output
    if output is not sys.stdout:
        output.close()

atexit(cleanup_output)

Tek satırınızın dosya nesnesini kapattığını sanmıyorum. Yanlış mıyım?
2rs2ts

1
@ 2rs2ts - Koşullu olarak ... yapar. Dosya nesnesinin refcount sıfıra gider çünkü ona işaret eden değişkenler yoktur, bu nedenle __del__ yönteminin çöp toplama gerçekleştiğinde hemen (cpython'da) veya daha sonra çağrılması mümkündür. Belgede bunun her zaman işe yarayacağına güvenmemek için uyarılar var ama ben her zaman daha kısa senaryolarda kullanıyorum. Uzun süre çalışan ve birçok dosya açan büyük bir şey ... sanırım 'with' veya 'try / nihayet' kullanırım.
tdelaney

TIL. Dosya nesnelerinin __del__bunu yapacağını bilmiyordum.
2rs2ts

@ 2rs2ts: CPython, bir referans sayma çöp toplayıcısı kullanır (gerektiğinde altında "gerçek" bir GC ile birlikte), böylece siz akış tutamacına tüm referansları bırakır bırakmaz dosyayı kapatabilir. Jython ve görünüşe göre IronPython yalnızca "gerçek" GC'ye sahiptir, bu nedenle nihai bir GC'ye kadar dosyayı kapatmazlar.
torek

0

Gerçekten daha "zarif" bir şeyde, yani tek satırlık bir şeyde ısrar etmeniz gerekiyorsa:

>>> 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.txtgörünür ve metni içerir foo.


Bu, CodeGolf StackExchange'e taşınmalıdır: D
kaiser

0

Sys.stdout için yeni bir fd açmaya ne dersiniz? Bu şekilde kapatırken herhangi bir sorun yaşamazsınız:

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

1
Ne yazık ki, bu python betiğini çalıştırdığımda kurulumumda bir sudo gerekiyor. / dev / stdout kök dizinine aittir.
Manur

Çoğu durumda, bir fd'nin stdout'a yeniden açılması beklenen bir şey değildir. Örneğin, bu kod stdout'u kısaltacak, böylece kabuk şeyler eklemek yerine dosyanın ./script.py >> file üzerine yazmak gibi şeyler yapacaktır.
salicideblock

Bu, / dev / stdout'u olmayan pencerelerde çalışmaz.
Bryan Oakley

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

Bazı durumlarda hafif gelişme.


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 veya üstünü kullanıyorsanız sadece iki ekstra satır: ekstra importiçin bir satır ve stack.enter_context.


0

Vücuttan sys.stdoutsonra kapalı olması sorun değilse with, aşağıdaki gibi kalıpları da kullanabilirsiniz:

# 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")

veya daha genel olarak:

with target if isinstance(target, io.IOBase) else open(target, "w") as f:
    f.write("hello world")

0

Aşağıdaki çözüm bir güzellik değil, çok uzun zaman öncesine ait; hemen önce ...

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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.