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.

3voto

romanows Points 390
import contextlib
import sys

with contextlib.ExitStack() as stack:
    h = stack.enter_context(open(target, 'w')) if target else sys.stdout
    h.write(content)

Il n'y a que deux lignes supplémentaires si vous utilisez Python 3.3 ou supérieur : une ligne pour l'option supplémentaire import et une ligne pour le stack.enter_context .

3voto

Stefaan Points 791

Si c'est bien que sys.stdout est fermé après with corps, vous pouvez également utiliser des modèles comme celui-ci :

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

ou même plus généralement :

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

1voto

Tommi Komulainen Points 771

J'opterais également pour une simple fonction d'encapsulation, qui peut être assez simple si vous pouvez ignorer le mode (et par conséquent stdin vs. stdout), par exemple :

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

1voto

tdelaney Points 7235

Ok, si nous entrons dans une guerre d'une ligne, voici :

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

J'aime l'exemple original de Jacob, tant que le contexte n'est écrit qu'à un seul endroit. Ce serait un problème si vous finissiez par rouvrir le fichier pour de nombreuses écritures. Je pense que je prendrais la décision une seule fois en haut du script et laisserais le système fermer le fichier à la sortie :

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

Vous pouvez inclure votre propre gestionnaire de sortie si vous pensez que c'est plus ordonné.

import atexit

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

atexit(cleanup_output)

0voto

2rs2ts Points 3310

Si vous devez vraiment insister sur quelque chose de plus "élégant", c'est-à-dire un texte d'une seule ligne :

>>> 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 apparaît et contient le texte 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