दोनों `को खुले (…)` और `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अशुद्ध भी बना सकते हैं , लेकिन यह बहुत अधिक नहीं लगता है:

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और बाद में इसे पुराने मान पर रीसेट कर सकते हैं। लेकिन ऐसा लगता है थोड़ा बहुत दूर ...
— 21

3
मैं वोट देने के बीच फटा हुआ हूं, "इसे छोड़ दो, तुम बिल्कुल बता सकते हो कि यह क्या कर रहा है" और भयावह अयोग्य सुझाव के लिए मतदान कर रहा है!
— GreenAsJade

8

जब आप ईएएफपी कर सकते हैं तो LBYL क्यों?

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

with/ का उपयोग करने के लिए इसे फिर से क्यों लिखेंasजब आप इसे एक जटिल तरीके से काम करना हो तो ब्लॉक को समान रूप ? आप अधिक लाइनें जोड़ेंगे और प्रदर्शन कम कर देंगे ।


3
अपवाद को दिनचर्या के "सामान्य" प्रवाह को नियंत्रित करने के लिए उपयोग नहीं किया जाना चाहिए । प्रदर्शन? बुदबुदाहट-अप एक त्रुटि तेजी से होगा कि अगर / और?
— जकूब एम।

2
इस संभावना पर निर्भर करता है कि आप एक या दूसरे का उपयोग कर रहे हैं।
— 2rs2ts

31
@JakubM। अपवाद पायथन में इस तरह से हो सकते हैं और होने चाहिए।
— गारेथ लेट्टी

13
यह देखते हुए कि पायथन का forलूप एक स्टॉपइंटरेशन एरर को पकड़कर फेंका गया है, जिसे इट्रेटर के द्वारा फेंका गया है।
— कर्क स्ट्रैसर

1
यह मानते हुए कि targetहै Noneजब sys.stdout का इरादा है, तो आप को पकड़ने के लिए की जरूरत TypeErrorके बजाय IOError।
— torek

5

एक अन्य संभावित समाधान: संदर्भ प्रबंधक से बाहर निकलने की विधि से बचने की कोशिश न करें, बस डुप्लिकेट डुप्लिकेट।

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 और openयदि filenameवास्तव में एक फ़ाइल नाम है, तो अंतिम विलुप्त होने वाले तर्कों को पारित करने की अनुमति देता है ।


1

मैं एक साधारण रैपर फंक्शन के लिए भी जाऊंगा, जो कि बहुत आसान हो सकता है यदि आप मोड को अनदेखा कर सकते हैं (और परिणामस्वरूप स्टड बनाम स्टडआउट), उदाहरण के लिए:

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

यह समाधान स्पष्ट रूप से फ़ाइल को क्लोज़ के साथ सामान्य या त्रुटि समाप्ति पर बंद नहीं करता है, इसलिए यह एक संदर्भ प्रबंधक के अधिक नहीं है। ऐसा वर्ग जो प्रवेश करता है और बाहर निकलता है, एक बेहतर विकल्प होगा।
— tdelaney

1
ValueError: I/O operation on closed fileअगर मुझे with open_or_stdout(..)ब्लॉक के बाहर फाइल लिखने की कोशिश की जाती है, तो मुझे मिल जाता है । मैं क्या खो रहा हूँ? sys.stdout को बंद करने का मतलब नहीं है।
— टॉमी कोमुलैनेन

1

ठीक है, अगर हम एक-लाइनर युद्धों में शामिल हो रहे हैं, तो यहां है:

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

मुझे नहीं लगता कि आपका वन-लाइनर फ़ाइल ऑब्जेक्ट को बंद करता है। क्या मै गलत हु?
— 2rs2ts

1
@ 2rs2ts - यह करता है ... सशर्त। फ़ाइल ऑब्जेक्ट का रिफ़काउंट शून्य हो जाता है क्योंकि इसमें कोई वैरिएबल इंगित नहीं होता है, इसलिए यह __del__ विधि के लिए उपलब्ध है जिसे या तो तुरंत (cpython में) या बाद में जब कचरा संग्रह होता है। डॉक्टर पर भरोसा नहीं करने के लिए चेतावनी दी गई है कि यह हमेशा काम करेगा लेकिन मैं इसे हर समय छोटी स्क्रिप्ट में उपयोग करता हूं। कुछ बड़ा जो लंबे समय तक चलता है और बहुत सारी फाइलें खोलता है ... अच्छी तरह से मुझे लगता है कि मैं 'के साथ' या 'कोशिश / अंत में' का उपयोग करूंगा।
— tdelaney

टीआईएल। मुझे नहीं पता था कि फ़ाइल ऑब्जेक्ट्स __del__ऐसा करेंगे।
— 2rs2ts

@ 2rs2ts: CPython एक संदर्भ-गिनती कचरा संग्राहक ("वास्तविक" जीसी के तहत आवश्यक के रूप में इस्तेमाल किया गया है) का उपयोग करता है, इसलिए यह स्ट्रीम-हैंडल के सभी संदर्भों को ड्रॉप करते ही फाइल को बंद कर सकता है। Jython और जाहिरा तौर पर 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।


इसे कोडगॉल्फ स्टैकएक्सचेंज: D
— kaiser

0

कैसे sys.stdout के लिए एक नई fd खोलने के बारे में? इस तरह आपको इसे बंद करने में कोई समस्या नहीं होगी:

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

1
अफसोस की बात है, इस अजगर स्क्रिप्ट को चलाने के लिए मेरे इंस्टॉल पर एक sudo चाहिए। / dev / stdout रूट के स्वामित्व में है।
— 15

कई स्थितियों में, स्टडआउट के लिए एक fd को फिर से खोलना वह नहीं है जो अपेक्षित है। उदाहरण के लिए, यह कोड stdcate को छोटा कर देगा, इस प्रकार शेल चीजों को बनाना है, जैसे कि इसे जोड़ने के बजाय फ़ाइल को ./script.py >> file अधिलेखित करना।
— साल्विकब्लॉक

यह उन विंडो पर काम नहीं करेगा जिनमें कोई / dev / stdout नहीं है।
— ब्रायन ओकले

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।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.