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?

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.


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

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

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.


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]

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:

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
...
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.