Jak mogę otworzyć wiele plików za pomocą „z otwartym” w Pythonie?

671

Chcę zmienić kilka plików w jednym czasie, wtedy i tylko wtedy mogę pisać do nich wszystkich. Zastanawiam się, czy w jakiś sposób mogę połączyć wiele otwartych wywołań ze withstwierdzeniem:

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

Jeśli nie jest to możliwe, jak wyglądałoby eleganckie rozwiązanie tego problemu?

Frantischeck003
źródło

Odpowiedzi:

1051

Począwszy od Python 2.7 (lub 3.1), możesz pisać

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

We wcześniejszych wersjach Pythona można czasem używać contextlib.nested()do zagnieżdżania menedżerów kontekstu. Nie będzie to jednak działać zgodnie z oczekiwaniami w przypadku otwierania plików wielokrotnych - szczegółowe informacje można znaleźć w dołączonej dokumentacji.


W rzadkim przypadku, gdy chcesz otworzyć jednocześnie zmienną liczbę plików, możesz użyć contextlib.ExitStack, począwszy od wersji Python 3.3:

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

Przez większość czasu masz zmienny zestaw plików, ale prawdopodobnie chcesz je otwierać jeden po drugim.

Sven Marnach
źródło
5
Niestety, zgodnie z dokumentami contextlib.nested, nie powinieneś używać go do otwierania plików: „użycie nested () do otwarcia dwóch plików jest błędem programowym, ponieważ pierwszy plik nie zostanie natychmiast zamknięty, jeśli zostanie zgłoszony wyjątek podczas otwierania drugi plik. ”
weronika
41
czy istnieje sposób na withotwarcie zmiennej listy plików?
monkut
23
@monkut: Bardzo dobre pytanie (możesz to zadać jako osobne pytanie). Krótka odpowiedź: Tak, istnieje ExitStackod wersji Python 3.3. Nie ma łatwego sposobu na zrobienie tego w żadnej wcześniejszej wersji Pythona.
Sven Marnach,
12
Czy można mieć tę składnię obejmującą wiele wierszy?
tommy.carstensen
9
@ tommy.carstensen: Możesz użyć zwykłych mechanizmów kontynuacji linii . Prawdopodobnie powinieneś użyć kontynuacji linii odwrotnego ukośnika, aby przerwać przecinek, zgodnie z zaleceniami PEP 9 .
Sven Marnach,
99

Wystarczy wymienić andz ,i gotowe:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Michał
źródło
3
Powinieneś określić, które wersje Pythona obsługują tę składnię.
Craig McQueen
58

Przy otwieraniu wielu plików jednocześnie lub w przypadku długich ścieżek plików przydatne może być podzielenie wielu wierszy. Z Przewodnika po stylu Python sugerowanego przez @Sven Marnach w komentarzach do innej odpowiedzi:

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
źródło
1
Z tym wcięciem otrzymuję: „flake8: linia kontynuacji nadmiernie wcięta dla wcięcia wizualnego”
Louis M
@LouisM To brzmi jak coś pochodzącego z twojego edytora lub środowiska, a nie podstawowy python. Jeśli nadal będzie to dla ciebie problem, zaleciłbym utworzenie nowego pytania z tym związanego i podanie bardziej szczegółowych informacji na temat edytora i środowiska.
Michael Ohlrogge
3
Tak, to zdecydowanie mój redaktor i jest to tylko ostrzeżenie. Chciałem podkreślić, że twoje wcięcie nie jest zgodne z PEP8. Powinieneś wciąć drugi open () 8 spacjami zamiast wyrównać go z pierwszym.
Louis M
2
@LouisM PEP8 jest wytyczną , a nie zasadami, w tym przypadku z pewnością bym to zignorował
Nick
2
Tak, nie ma z tym problemu, może być przydatny dla innych osób z automatycznymi włóknami :)
Louis M
15

Zagnieżdżone w oświadczeniach wykonają tę samą pracę i moim zdaniem łatwiej sobie z nimi poradzić.

Załóżmy, że masz plik inFile.txt i chcesz zapisać go jednocześnie w dwóch plikach 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)

EDYTOWAĆ:

Nie rozumiem przyczyny przegłosowania. Przetestowałem mój kod przed opublikowaniem mojej odpowiedzi i działa on zgodnie z oczekiwaniami: Zapisuje się do wszystkich plików outFile, zgodnie z pytaniem. Brak duplikatów lub brak zapisu. Jestem więc naprawdę ciekawy, dlaczego moja odpowiedź jest uważana za błędną, nieoptymalną lub coś w tym rodzaju.

FatihAkici
źródło
1
nie wiem, co ktoś cię ocenił, ale ZAPRASZALIŁEM cię, ponieważ jest to jedyny przykład z trzema plikami (jedno wejście, dwa dane wyjściowe), które okazały się być dokładnie tym, czego potrzebowałem.
Adam Michael Wood
2
być może jesteś zaniedbany bcoz w Pythonie> 2.6 możesz napisać więcej kodu pythonowego - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (dlaczego nie mogę wstawić fragmentu kodu w komentarzach ?!) Jesteśmy w 2018 roku;) tak starożytne wersje w przeszłość
El Ruso
2
Przyjazne przypomnienie dla tych pooh-puchatków Python 2.6: CentOS 6 (który działa dopiero po EOL do listopada 2020), nadal domyślnie używa py2.6. Tak więc ta odpowiedź jest (jak na razie) wciąż najlepsza ogólnie dla IMO.
BJ Black
11

Od wersji Python 3.3 możesz używać klasy ExitStackz contextlibmodułu do bezpiecznego
otwierania dowolnej liczby plików .

Może zarządzać dynamiczną liczbą obiektów kontekstowych, co oznacza, że ​​okaże się szczególnie przydatny, jeśli nie wiesz, ile plików zamierzasz obsłużyć .

W rzeczywistości kanoniczny przypadek użycia wspomniany w dokumentacji zarządza dynamiczną liczbą plików.

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

Jeśli jesteś zainteresowany szczegółami, oto ogólny przykład wyjaśniający, jak ExitStackdziała:

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)

Wynik:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]
timb
źródło
3

Z Pythonem 2.6 To nie zadziała, musimy użyć poniższego sposobu, aby otworzyć wiele plików:

with open('a', 'w') as a:
    with open('b', 'w') as b:
Aashutosh jha
źródło
1

Późna odpowiedź (8 lat), ale dla kogoś, kto chce połączyć wiele plików w jeden , pomocna może być następująca funkcja:

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
źródło