it-swarm.asia

كيفية التعامل مع كلاهما (بـ open (...) `و` sys.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 خطوط

75
Jakub M.

مجرد التفكير خارج الصندوق هنا ، ماذا عن طريقة 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()

استخدمه مثل هذا:

# writes to some_file
with smart_open('some_file') as fh:
    print >>fh, 'some output'

# writes to stdout
with smart_open() as fh:
    print >>fh, 'some output'

# writes to stdout
with smart_open('-') as fh:
    print >>fh, 'some output'
76
Wolph

العصا مع الكود الحالي. إنه أمر بسيط ويمكنك معرفة بالضبط ما الذي تفعله فقط من خلال إلقاء نظرة خاطفة عليه.

هناك طريقة أخرى ستكون بها if:

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

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

ولكن هذا ليس أقصر بكثير مما لديك ويبدو أنه يمكن القول أسوأ.

يمكنك أيضًا جعل sys.stdout unclosable ، لكن هذا لا يبدو Pythonic أيضًا:

sys.stdout.close = lambda: None

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

لماذا LBYL عندما يمكنك EAFP؟

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

لماذا أعد كتابته لاستخدام كتلة with/as بشكل موحد عندما تضطر إلى جعله يعمل بطريقة ملتوية؟ ستقوم بإضافة خطوط أكثر وتقليل الأداء.

6
2rs2ts

تحسين إجابة وولف

import sys
import contextlib

@contextlib.contextmanager
def smart_open(filename: str = None, mode: str = 'r', *args, **kwargs):
    if filename == '-':
        if 'r' in mode:
            stream = sys.stdin
        else:
            stream = sys.stdout
        if 'b' in mode:
            fh = stream.buffer
        else:
            fh = stream
    else:
        fh = open(filename, mode, *args, **kwargs)

    try:
        yield fh
    finally:
        try:
            fh.close()
        except AttributeError:
            pass

هذا يسمح بالثنائي IO ويمرر الوسيطات الخارجية الغريبة إلى open إذا filename هو بالفعل اسم ملف.

3
Evpok

حل آخر ممكن: لا تحاول تجنب طريقة خروج مدير السياق ، فما عليك سوى تكرار stdout.

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

سأذهب أيضًا إلى وظيفة التفاف بسيطة ، والتي يمكن أن تكون بسيطة جدًا إذا كنت تستطيع تجاهل الوضع (وبالتالي 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
2
Tommi Komulainen

ماذا عن فتح fd جديد لـ sys.stdout؟ بهذه الطريقة لن تواجه أي مشكلة في إغلاقه:

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

حسنًا ، إذا وقعنا في حروب ذات خطوط واحدة ، فإليك:

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

إذا كان يجب عليك حقًا الإصرار على شيء أكثر "أناقة" ، على سبيل المثال خط واحد:

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

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

تحسن طفيف في بعض الحالات.

0
Eugene K