How to handle both `with open(…)` and `sys.stdout` nicely?

后端 未结 13 745
长发绾君心
长发绾君心 2020-12-07 14:47

Often I need to output data either to file or, if file is not specified, to stdout. I use the following snippet:

if target:
    with open(target, \'w\') as h         


        
相关标签:
13条回答
  • 2020-12-07 15:04

    Just thinking outside of the box here, how about a custom open() method?

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

    Use it like this:

    # 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)
    
    0 讨论(0)
  • 2020-12-07 15:08

    Stick with your current code. It's simple and you can tell exactly what it's doing just by glancing at it.

    Another way would be with an inline if:

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

    But that isn't much shorter than what you have and it looks arguably worse.

    You could also make sys.stdout unclosable, but that doesn't seem too Pythonic:

    sys.stdout.close = lambda: None
    
    with (open(target, 'w') if target else sys.stdout) as handle:
        handle.write(content)
    
    0 讨论(0)
  • 2020-12-07 15:10

    Why LBYL when you can EAFP?

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

    Why rewrite it to use the with/as block uniformly when you have to make it work in a convoluted way? You'll add more lines and reduce performance.

    0 讨论(0)
  • 2020-12-07 15:13

    If you really must insist on something more "elegant", i.e. a one-liner:

    >>> 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 appears and contains the text foo.

    0 讨论(0)
  • 2020-12-07 15:15

    An improvement of Wolph's answer

    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
    

    This allows binary IO and pass eventual extraneous arguments to open if filename is indeed a file name.

    0 讨论(0)
  • 2020-12-07 15:15

    I'd also go for a simple wrapper function, which can be pretty simple if you can ignore the mode (and consequently stdin vs. stdout), for example:

    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
    
    0 讨论(0)
提交回复
热议问题