124 votes

Comment gérer agréablement `with open(...)` et `sys.stdout` ?

J'ai souvent besoin de sortir des données soit vers un fichier, soit, si le fichier n'est pas spécifié, vers stdout. J'utilise le snippet suivant :

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

Je voudrais le réécrire et traiter les deux cibles de manière uniforme.

Dans le cas idéal, ce serait :

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

mais cela ne fonctionnera pas bien car sys.stdout sera fermé en quittant with et je ne veux pas de ça. Je ne veux pas non plus

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

parce que je devrais me souvenir de restaurer le stdout original.

En rapport :

Modifier

Je sais que je peux emballer target définir une fonction ou un usage distinct gestionnaire de contexte . Je cherche une solution simple, élégante et idiomatique qui ne nécessite pas plus de 5 lignes.

117voto

Wolph Points 28062

Je sors un peu des sentiers battus, que diriez-vous d'un projet personnalisé open() méthode ?

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

Utilisez-le comme ça :

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

37voto

Blender Points 114729

Restez avec votre code actuel. C'est simple et vous pouvez dire exactement ce qu'il fait juste en le regardant.

Une autre façon de procéder serait d'utiliser une ligne if :

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

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

Mais ce n'est pas beaucoup plus court que ce que vous avez et ça a l'air bien pire.

Vous pouvez également faire sys.stdout non fermable, mais cela ne semble pas trop pythique :

sys.stdout.close = lambda: None

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

12voto

Evpok Points 1214

Une amélioration de Réponse de Wolph

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

Cela permet d'effectuer des entrées/sorties binaires et de passer d'éventuels arguments étrangers à l'utilisateur. open si filename est bien un nom de fichier.

10voto

2rs2ts Points 3310

Pourquoi la LBYL quand on peut la EAFP ?

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

Pourquoi le réécrire pour utiliser le with / as bloc de manière uniforme alors que vous devez le faire fonctionner de manière alambiquée ? Vous ajouterez plus et réduire les performances.

5voto

Olivier Aubert Points 11

Une autre solution possible : n'essayez pas d'éviter la méthode de sortie du gestionnaire de contexte, dupliquez simplement stdout.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X