Як я можу відкрити кілька файлів, використовуючи «з відкритим» у Python?


671

Я хочу змінити пару файлів за один раз, якщо я можу записати їх до всіх. Мені цікаво, чи я можу якось поєднати кілька відкритих дзвінків із withзаявою:

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

Якщо це неможливо, як виглядатиме елегантне рішення цієї проблеми?

Відповіді:


1051

Станом на Python 2.7 (або 3.1 відповідно) ви можете писати

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

У більш ранніх версіях Python іноді можна використовувати contextlib.nested()для управління гніздовими контекстами. Це не працюватиме, як очікувалося, для відкриття декількох файлів, однак - детальну інформацію див. У зв'язаній документації.


У рідкісному випадку, коли ви хочете відкрити змінну кількість файлів одночасно, ви можете використовувати contextlib.ExitStack, починаючи з версії 3.3 в Python:

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

Більшу частину часу у вас є змінний набір файлів, ви, ймовірно, хочете відкрити їх один за одним, хоча.


5
На жаль, згідно з документами contextlib.nested, ви не повинні використовувати його для відкриття файлу: "використання вкладеного () для відкриття двох файлів - це помилка програмування, оскільки перший файл не буде закритий негайно, якщо викид буде викинуто при відкритті другий файл. "
weronika

41
чи є спосіб використовувати withдля відкриття змінного списку файлів?
monkut

23
@monkut: Дуже хороше запитання (ви насправді можете задати це як окреме запитання). Коротка відповідь: Так, існує ExitStackв Python 3.3. Немає простого способу зробити це в будь-якій більш ранній версії Python.
Свен Марнах

12
Чи можливо, щоб цей синтаксис охоплював кілька рядків?
tommy.carstensen

9
@ tommy.carstensen: Ви можете використовувати звичайні механізми продовження рядків . Ви , ймовірно , слід використовувати зворотну косу риску продовження лінії розбити на коми, як і було рекомендовано PEP 9 .
Свен Марнах

99

Просто замініть andз , ,і ви зробили:

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
Ви повинні вказати, які версії Python підтримують цей синтаксис.
Крейг МакКуїн

58

Для відкриття багатьох файлів одночасно або для довгих шляхів до файлів може бути корисно розбити речі на кілька рядків. З посібника зі стилів Python, як запропонував @Sven Marnach у коментарях до іншої відповіді:

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
З цим відступом я отримую: "flake8: рядок продовження перекреслений для візуального відступу"
Louis M

@LouisM Це звучить як щось із вашого редактора чи оточення, а не базовий пітон. Якщо це все ще буде проблемою для вас, я рекомендую створити нове запитання, яке стосується цього, та детальніше розповісти про ваш редактор та оточення.
Майкл Ольрогге

3
Так, це безумовно мій редактор, і це лише попередження. Я хотів наголосити на тому, що ваш відступ не відповідає PEP8. Ви повинні відступити другий open () з 8 пробілами замість того, щоб вирівнювати його з першим.
Луї М

2
@LouisM PEP8 - це керівництво , а не правила, і в цьому випадку я б, безумовно, ігнорував це
Нік

2
Так, з цим немає жодних проблем, це може бути корисно і іншим людям з автоматичними вкладишами :)
Louis M

15

Вкладені з висловлюваннями будуть виконувати ту саму роботу, і, на мою думку, більш просто.

Скажімо, у вас є inFile.txt, і хочете записати його на два 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)

Редагувати:

Я не розумію причини протистояння. Я перевірив свій код, перш ніж публікувати свою відповідь, і він працює, як бажано: Він пише всім користувачам OutFile, як і запитання. Немає дублікатів або не вдається записати. Тож мені дуже цікаво знати, чому моя відповідь вважається неправильною, неоптимальною чи що-небудь подібним.


1
я не знаю, що хтось інший образив вас, але я ЗАПРОШУЮ вас, тому що це єдиний приклад, який мав три файли (один вхід, два вихідні), які траплялися саме тим, що мені потрібно.
Адам Майкл Вуд

2
можливо, вам принижений bcoz в Python> 2.6, ви можете написати більше пітонічного коду - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (чому я не можу вставити фрагмент коду в коментарі ?!) Ми в 2018 році; минулого
Ель Русо

2
Дружнє нагадування для тих, хто poo-poohing python 2.6: CentOS 6 (який не EOL до листопада 2020 року), як і раніше, використовує py2.6 за замовчуванням. Тож ця відповідь (на даний момент) все ще є найкращим загальним ІМО.
BJ Чорний

11

Оскільки Python 3.3, ви можете використовувати клас ExitStackз contextlibмодуля для безпечного
відкриття довільної кількості файлів .

Він може керувати динамічною кількістю об'єктів, що знають контекст, а це означає, що виявиться особливо корисним, якщо ви не знаєте, скільки файлів ви збираєтеся обробити .

Насправді, канонічний випадок використання, який згадується в документації, управляє динамічною кількістю файлів.

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

Якщо вас цікавлять деталі, ось загальний приклад, щоб пояснити, як ExitStackпрацює:

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)

Вихід:

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

3

З python 2.6 це не буде працювати, ми повинні використовувати спосіб відкриття декількох файлів нижче:

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

1

Пізня відповідь (8 років), але для тих, хто хоче об'єднати кілька файлів в один , може допомогти наступна функція:

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
...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.