Comment puis-je ouvrir plusieurs fichiers en utilisant «avec open» en Python?

672

Je souhaite modifier quelques fichiers à la fois, si je ne peux pas les écrire tous. Je me demande si je peux en quelque sorte combiner les multiples appels ouverts avec la withdéclaration:

try:
  with open('a', 'w') as a and open('b', 'w') as b:
    do_something()
except IOError as e:
  print 'Operation failed: %s' % e.strerror

Si ce n'est pas possible, à quoi ressemblerait une solution élégante à ce problème?

Frantischeck003
la source

Réponses:

1052

Depuis Python 2.7 (ou 3.1 respectivement), vous pouvez écrire

with open('a', 'w') as a, open('b', 'w') as b:
    do_something()

Dans les versions antérieures de Python, vous pouvez parfois utiliser contextlib.nested()pour imbriquer des gestionnaires de contexte. Cela ne fonctionnera pas comme prévu pour ouvrir plusieurs fichiers, cependant - voir la documentation liée pour plus de détails.


Dans les rares cas où vous souhaitez ouvrir un nombre variable de fichiers en même temps, vous pouvez utiliser contextlib.ExitStack, à partir de Python version 3.3:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Do something with "files"

La plupart du temps, vous disposez d'un ensemble variable de fichiers, mais vous voudrez probablement les ouvrir l'un après l'autre.

Sven Marnach
la source
5
Malheureusement, selon les documents contextlib.nested, vous ne devez pas l'utiliser pour l'ouverture de fichiers: "l'utilisation de nested () pour ouvrir deux fichiers est une erreur de programmation car le premier fichier ne sera pas fermé rapidement si une exception est levée lors de l'ouverture du fichier. deuxième fichier. "
weronika
41
existe-t-il un moyen d'utiliser withpour ouvrir une liste variable de fichiers?
Monkut
23
@monkut: Très bonne question (vous pourriez en fait poser cette question séparément). Réponse courte: Oui, il existe à ExitStackpartir de Python 3.3. Il n'y a aucun moyen facile de le faire dans une version antérieure de Python.
Sven Marnach
12
Est-il possible d'avoir cette syntaxe sur plusieurs lignes?
tommy.carstensen
9
@ tommy.carstensen: Vous pouvez utiliser les mécanismes habituels de continuation de ligne . Vous devriez probablement utiliser la continuation de la ligne de barre oblique inverse pour rompre la virgule, comme recommandé par PEP 9 .
Sven Marnach du
99

Remplacez simplement andpar ,et vous avez terminé:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Michael
la source
3
Vous devez spécifier quelles versions de Python prennent en charge cette syntaxe.
Craig McQueen
58

Pour ouvrir plusieurs fichiers à la fois ou pour de longs chemins d'accès, il peut être utile de répartir les choses sur plusieurs lignes. Du guide de style Python comme suggéré par @Sven Marnach dans les commentaires à une autre réponse:

with open('/path/to/InFile.ext', 'r') as file_1, \
     open('/path/to/OutFile.ext', 'w') as file_2:
    file_2.write(file_1.read())
Michael Ohlrogge
la source
1
Avec cette indentation, j'obtiens: "flake8: ligne de continuation indentée pour le retrait visuel"
Louis M
@LouisM Cela ressemble à quelque chose provenant de votre éditeur ou de votre environnement, plutôt que de python de base. Si cela continue d'être un problème pour vous, je vous recommande de créer une nouvelle question à ce sujet et de donner plus de détails sur votre éditeur et votre environnement.
Michael Ohlrogge
3
Oui, c'est certainement mon éditeur, et ce n'est qu'un avertissement. Ce que je voulais souligner, c'est que votre indentation n'est pas conforme à PEP8. Vous devez mettre en retrait le deuxième open () avec 8 espaces au lieu de l'aligner avec le premier.
Louis M
2
@LouisM PEP8 est une directive , pas des règles, et dans ce cas je l'ignorerais très certainement
Nick
2
Oui pas de problème avec ça, cela pourrait être utile pour d'autres personnes avec des linters automatiques cependant :)
Louis M
15

Les instructions imbriquées feront le même travail et, à mon avis, sont plus simples à gérer.

Supposons que vous ayez inFile.txt et que vous souhaitiez l'écrire simultanément dans deux outFile.

with open("inFile.txt", 'r') as fr:
    with open("outFile1.txt", 'w') as fw1:
        with open("outFile2.txt", 'w') as fw2:
            for line in fr.readlines():
                fw1.writelines(line)
                fw2.writelines(line)

ÉDITER:

Je ne comprends pas la raison du downvote. J'ai testé mon code avant de publier ma réponse, et cela fonctionne comme vous le souhaitez: il écrit dans tous les fichiers outFile, tout comme la question le demande. Aucune écriture en double ou échec d'écriture. Je suis donc vraiment curieux de savoir pourquoi ma réponse est considérée comme fausse, sous-optimale ou quelque chose comme ça.

FatihAkici
la source
1
Je ne sais pas ce que quelqu'un d'autre a voté contre vous, mais je vous ai mis à jour parce que c'est le seul exemple qui avait trois fichiers (une entrée, deux sorties) qui se trouvaient être exactement ce dont j'avais besoin.
Adam Michael Wood
2
peut-être que vous êtes rétrogradé bcoz en Python> 2.6, vous pouvez écrire plus de code pythonique - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (pourquoi je ne peux pas insérer de fragment de code dans les commentaires?!) Nous sommes en 2018;) donc des versions anciennes dans le passé
El Ruso
2
Un rappel amical à ces python poo-poohing 2.6: CentOS 6 (qui ne prend fin qu'en novembre 2020), utilise toujours py2.6 par défaut. Donc, cette réponse est (en ce moment) la meilleure dans l'ensemble IMO.
BJ Black
11

Depuis Python 3.3, vous pouvez utiliser la classe ExitStackdu contextlibmodule pour
ouvrir en toute sécurité un nombre arbitraire de fichiers .

Il peut gérer un nombre dynamique d'objets contextuels, ce qui signifie qu'il s'avérera particulièrement utile si vous ne savez pas combien de fichiers vous allez gérer .

En fait, le cas d'utilisation canonique mentionné dans la documentation gère un nombre dynamique de fichiers.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Si vous êtes intéressé par les détails, voici un exemple générique pour expliquer comment ExitStackfonctionne:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(len(stack._exit_callbacks)) # number of callbacks called on exit
    nums = [stack.enter_context(x) for x in xs]
    print(len(stack._exit_callbacks))

print(len(stack._exit_callbacks))
print(nums)

Production:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]
timgeb
la source
3

Avec python 2.6 Cela ne fonctionnera pas, nous devons utiliser ci-dessous pour ouvrir plusieurs fichiers:

with open('a', 'w') as a:
    with open('b', 'w') as b:
Aashutosh jha
la source
1

Réponse tardive (8 ans), mais pour quelqu'un qui cherche à joindre plusieurs fichiers en un seul , la fonction suivante peut être utile:

def multi_open(_list):
    out=""
    for x in _list:
        try:
            with open(x) as f:
                out+=f.read()
        except:
            pass
            # print(f"Cannot open file {x}")
    return(out)

fl = ["C:/bdlog.txt", "C:/Jts/tws.vmoptions", "C:/not.exist"]
print(multi_open(fl))

2018-10-23 19:18:11.361 PROFILE  [Stop Drivers] [1ms]
2018-10-23 19:18:11.361 PROFILE  [Parental uninit] [0ms]
...
# This file contains VM parameters for Trader Workstation.
# Each parameter should be defined in a separate line and the
...
CONvid19
la source