Оператор контекста with
В прошлый раз я много раз обратил внимание на то, что файлы надо закрывать после того, как завершена работа. Но в реальности в большой программе отслеживать закрыт ли файл становится достаточно проблематично. Бывает вы открываете файл в одной части программы, потом передаете его в качестве параметра в функцию, где он должен быть обработан построчно, а строки могут быть переданы еще куда-то и в результате можно просто забыть закрыть файл. И как следствие либо получить ситуацию, когда у вас открыто множество файлов, которые уже не нужны, либо перестараться и закрыть файл до того, как из него будут прочитаны все данные и получать сообщение об ошибке.
Эту ситуацию можно было бы избежать если обрабатывать файл не построчно, а сразу загрузить весь объем в оперативную память с помощью метода read(). Но этот метод будет работать до первого большого файла.
Чтобы избежать подобные ситуации я хочу вам предложить способ с помощью менеджера контекста with. Эта тема определенно относится к более продвинутому курсу языка, но лучше сразу сформировать привычку, которая в последствии окупится много раз.
Работа с файлами в Python достаточно прямолинейна с точки зрения необходимых действий. Вы открываете файл функцией open она возвращает специальный файловый объект, с которым осуществляются операции ввода-вывода и потом обязательно вызывается метод close() для того, чтобы закрыть работу с потоком. Как оказалось это очень распространенный шаблон работы, например с файлами или сетевыми соединениями, создается объект, который нужен непродолжительное время, но у которого обязательно нужно вызвать методы закрытия. Именно так и работает менеджер контекста with.
Синтаксис конструкции выглядит так:
with <expr1> as <name1>, [expr2 as name2, ...]:
<block contents that can use name>
Выражение expr1 возвращает объект, который можно записать в переменную name1, когда блок завершит работу, то у переменной name1 вызовется специальный метод закрытия, если это файл, то метод вызовет метод close(). Если необходимо в одном контексте создать сразу несколько переменных, например открыть несколько файлов, то их можно указывать через запятую.
Вот измененный код программы, которая выводит содержимое текста на экран построчно Файл reader2py:
with open("turing_paper_1936.txt", "rt") as paper:
for line in paper:
print(line[:-1])
Больше нет необходимости добавлять paper.close() после тела цикла for. И самое главное, что с таким кодом гораздо легче не совершать лишних ошибок.
Под капотом with
Менеджер контекста внутри работает следующим образом: после того выражение expr1 создает переменную, то вызывается магический метод __enter__, после завершения блока вызывается метод __exit__. Файловый объект имеет эти методы специально для того, чтобы работать с with. Если вам интересно посмотреть на реализацию подобного механизма самостоятельно, то можете взять в качестве примера такой код:
class context:
def __enter__(self):
# подготовка объекта
return self # то, что тут вернется будет присвоенно переменной, стоящей после as
def __exit__(self, type, value, traceback):
# код отвечающий за выход из контекста, например тут может находится вызов self.close()
# обработка ошибок если они возникли
pass
with context() as obj:
# работа с объектом obj
В действительности with нечто среднее между try/except и циклом тело которого выполняется только один раз.
Еще один пример копирует содержимое файла turing_paper_1936.txt в файл turing_paper_copy.txt.
Файл new_copy.py:
with open("turing_paper_1936.txt", "rt") as in_file, open(
"turing_copy.txt", "wt"
) as out_file:
for line in in_file:
out_file.write(line)
С менеджером контекста with гораздо проще. Не надо следить за тем, чтобы закрыть оба файла и конечный код содержит гораздо меньше лишних строк кода.
Задание: Чтение и запись в файл.
В этом блоке вы будете учиться работать с файлами. Чтобы лучше контролировать процесс, вам понадобится использовать IDE. Просто скопируйте туда код задания и выполните его.
Неприхотливое и бессмысленное задание. Прочитать из файла несколько строк и записать их в другой файл в обратном порядке.
- Имена файлов для чтения и записи уже определены. Не меняйте их.
- Используйте менеджер контекста with.
Код задания: (для копирования в IDE):
FILENAME_READ = 'data1.txt'
FILENAME_WRITE = 'data2.txt'
def readwrite():
# Write your code here
Для проверки своего кода:
Для файла data1.txt
55555
4444
333
22
1
Ожидается data2.txt
1
22
333
4444
55555