Глава 11. Файлы

Добавлено 14 июня 2020 в 21:49

Содержание главы

Погружение

До установки одного приложения на моем ноутбуке с Windows было 38 493 файла. Установка Python 3 добавила к ним еще почти 3000 файлов. Файлы представляют собой первичную парадигму хранения информации в основных операционных системах; эта концепция настолько укоренилась, что большинство людей не воспримут нечто другое альтернативное. Образно говоря, ваш компьютер тонет в море файлов.

11.2 Чтение из текстовых файлов

Прежде чем читать из текстового файла, его требуется открыть. Открытие файла в Python выполнить легко:

a_file = open('examples/chinese.txt', encoding='utf-8')

В Python есть встроенная функция open(), которой в качестве аргумента передается имя файла. В этом примере имя файла – 'examples/chinese.txt', и в нём есть 5 интересных вещей:

  1. Это не просто имя файла, это комбинация пути к каталогу и имя файла. Гипотетическая функция открытия файла могла бы иметь два аргумента (путь к каталогу и имя файла), но функция open() принимает только один параметр. В Python, когда вам необходимо «имя файла», вы можете использовать либо полный, либо частичный путь к каталогу.
  2. При указании пути к каталогу используется / (прямой слеш), независимо от операционной системы. Windows для указания пути к подкаталогам использует \ (обратный слеш), а операционные системы Linux и MacOS используют / (прямой слеш). В Python прямой слеш работает всегда, даже на Windows.
  3. Путь каталога не начинается с косой черты (слеша) или буквы диска, поэтому это называется относительным путем. Относительно чего? Имейте терпение!
  4. Это строки. Все современные операционные системы (включая Windows) для хранения имён файлов и каталогов используют Unicode. Python 3 полностью поддерживает не-ASCII пути.
  5. Файл не обязательно должен находиться на вашем локальном диске. Вы можете использовать сетевые диски. Этот «файл» может быть объектом виртуальной файловой системы. Если ваш компьютер считает «это» файлом и даёт возможность обращаться к нему как к файлу, то Python сможет это открыть.

Вызов функции open() не ограничивается передачей параметра пути к файлу и его имени. Имеется ещё один параметр, называющийся encoding. О да, дорогой читатель, это звучит воистину ужасно!

11.2.1 Особенности кодировки показывают своё страшное лицо

Байты – это байты; символы – это абстракция. Строка – это последовательность символов Unicode. Но файл на диске не является последовательностью символов Unicode; файл на диске – это последовательность байтов. Итак, если вы читаете с диска «текстовый файл», как Python преобразует эту последовательность байтов в последовательность символов? Он декодирует байты в соответствии с определенным алгоритмом кодирования символов и возвращает последовательность символов Unicode (иначе называемую строкой).

# Этот пример был создан в Windows. Другие платформы могут
# вести себя по-другому по причинам, изложенным ниже.
>>> file = open('examples/chinese.txt')
>>> a_string = file.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 28: character maps to <undefined>
>>> 

Что сейчас произошло? Вы не указали кодировку символов, поэтому Python вынужден использовать кодировку по умолчанию. Какая кодировка используется по умолчанию? Если вы внимательно посмотрите на трассировку, вы увидите, что она умирает на cp1252.py, что означает, что в качестве кодировки по умолчанию Python использует CP-1252 (CP-1252 – это распространенная кодировка на компьютерах под управлением Microsoft Windows). Набор символов CP-1252 не поддерживает символы, содержащиеся в этом файле, поэтому чтение завершается неудачей с уродливой ошибкой UnicodeDecodeError.

Но подождите, есть кое-что еще хуже! Кодировка по умолчанию зависит от платформы, поэтому этот код может работать на вашем компьютере (если кодировкой по умолчанию является UTF-8), но сбой произойдет, когда вы распространите его среди других (чья кодировка по умолчанию отличается, например, CP-1252).

Кодировка по умолчанию зависит от платформы.

Если вам нужно получить кодировку символов по умолчанию, импортируйте модуль locale и вызовите метод locale.getpreferredencoding(). На моем ноутбуке с Windows он возвращает 'cp1252', но на моем компьютере с Linux он возвращает 'UTF8'. Я даже не могу поддерживать согласованность в своем собственном доме! Ваши результаты могут отличаться (даже в Windows), в зависимости от того, какую версию операционной системы вы установили, и как сконфигурированы ваши региональные/языковые настройки. Вот почему так важно указывать кодировку каждый раз, когда вы открываете файл.

11.2.2 Потоковые объекты

Пока что всё, что мы узнали, это то, что в Python есть встроенная функция open(). Эта функция open() возвращает потоковый объект, у которого есть методы и атрибуты для получения информации о потоке символов и управления им.

>>> a_file = open('examples/chinese.txt', encoding='utf-8')
>>> a_file.name                                              ①
'examples/chinese.txt'
>>> a_file.encoding                                          ②
'utf-8'
>>> a_file.mode                                              ③
'r'
  1. Строка 2. Атрибут name отражает имя, которое вы передали функции open() при открытии файла. Он не нормализован к абсолютному пути.
  2. Строка 4. Аналогично, атрибут encoding отражает кодировку, переданную вами в функцию open(). Если вы не указали кодировку при открытии файла (плохой разработчик!), то атрибут encoding будет отражать locale.getpreferredencoding().
  3. Строка 6. Атрибут mode сообщает, в каком режиме был открыт файл. Вы можете передать необязательный параметр mode в функцию open(). При открытии этого файла мы не указали режим, поэтому Python присваивает значение по умолчанию 'r', что означает «открыть только для чтения, в текстовом режиме». Как вы увидите позже в этой главе, режим файла служит нескольким целям; различные режимы позволяют записывать в файл, добавлять в файл или открывать файл в двоичном режиме (в котором вы работаете с байтами, а не со строками).

В документации по функции open() перечислены все возможные режимы файлов.

11.2.3 Чтение данных из текстового файла

После того, как вы открыли файл для чтения, в какой-то момент вы, вероятно, захотите его прочитать.

>>> a_file = open('examples/chinese.txt', encoding='utf-8')
>>> a_file.read()                                            ①
'Dive Into Python 是为有经验的程序员编写的一本 Python 书。\n'
>>> a_file.read()                                            ②
''
  1. Строка 2. После открытия файла (с правильной кодировкой) чтение из него – это просто вызов метода read() потокового объекта. Результатом является строка.
  2. Строка 4. Возможно, несколько удивительно, что повторное чтение файла не вызывает исключений. Python не считает чтение за концом файла ошибкой; он просто возвращает пустую строку.

Всегда указывайте параметр кодировки при открытии файла.

Что, если вы хотите перечитать файл?

# продолжение предыдущего примера
>>> a_file.read()                      ①
''
>>> a_file.seek(0)                     ②
0
>>> a_file.read(16)                    ③
'Dive Into Python'
>>> a_file.read(1)                     ④
' '
>>> a_file.read(1)
'是'
>>> a_file.tell()                      ⑤
20
  1. Строка 2. Поскольку вы всё еще находитесь в конце файла, дальнейшие вызовы метода read() потокового объекта просто возвращают пустую строку.
  2. Строка 4. Метод seek() перемещает в определенную байтовую позицию в файле.
  3. Строка 6. Метод read() может принимать необязательный параметр, количество символов для чтения.
  4. Строка 8. Если хотите, вы можете прочитать за раз даже только один символ.
  5. Строка 12. 16 + 1 + 1 =… 20?

Давайте попробуем снова.

# продолжение предыдущего примера
>>> a_file.seek(17)                    ①
17
>>> a_file.read(1)                     ②
'是'
>>> a_file.tell()                      ③
20
  1. Строка 2. Перейти к 17-му байту.
  2. Строка 4. Прочитать один символ.
  3. Строка 6. Теперь мы находимся на 20 байте.

Вы уже видели это? Методы seek() и tell() всегда считают байты, но поскольку вы открыли этот файл как текст, метод read() считает символы. Китайским символам для кодирования в UTF-8 требуется несколько байтов. Английские символы в файле требуют только одного байта на каждый символ, поэтому вы можете ошибочно подумать, что методы seek() и read() отсчитывают одно и то же значение. Но это верно только для некоторых символов.

Но подождите, всё становится еще хуже!

>>> a_file.seek(18)                         ①
18
>>> a_file.read(1)                          ②
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    a_file.read(1)
  File "C:\Python31\lib\codecs.py", line 300, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x98 in position 0: unexpected code byte
  1. Строка 1. Перейти к 18-му байту и попробовать прочитать один символ.
  2. Строка 3. Почему это не удается? Потому что в 18-м байте нет символа. Ближайший символ начинается с 17-го байта (и занимает три байта). Попытка прочитать символ с середины заканчивается неудачей с исключением UnicodeDecodeError.

11.2.4 Закрытие файлов

Открытые файлы потребляют системные ресурсы, и в зависимости от режима открытия файлов другие программы могут не иметь к ним доступа. Поэтому, как только вы закончили работу с файлом, важно его закрыть.

# продолжение предыдущего примера
>>> a_file.close()

Ну, это было не неожиданно.

Потоковый объект a_file всё еще существует; вызов метода close() не уничтожает сам объект. А это не очень полезно.

# продолжение предыдущего примера
>>> a_file.read()                           ①
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    a_file.read()
ValueError: I/O operation on closed file.
>>> a_file.seek(0)                          ②
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    a_file.seek(0)
ValueError: I/O operation on closed file.
>>> a_file.tell()                           ③
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    a_file.tell()
ValueError: I/O operation on closed file.
>>> a_file.close()                          ④
>>> a_file.closed                           ⑤
True
  1. Строка 2. Вы не можете читать из закрытого файла; это вызывает исключение IOError.
  2. Строка 7. Вы также не можете перемещаться по закрытому файлу.
  3. Строка 12. В закрытом файле нет текущей позиции, поэтому метод tell() также не работает.
  4. Строка 17. Возможно, это удивительно, но вызов метода close() для потокового объекта, файл которого был закрыт, не вызывает исключения. Это просто пустая операция.
  5. Строка 18. У закрытых потоковых объектов есть один полезный атрибут: атрибут closed подтвердит, что файл закрыт.

11.2.5 Автоматическое закрытие файлов

try..finally – это хорошо. with – еще лучше.

Потоковые объекты имеют явный метод close(), но что произойдет, если в вашем коде будет ошибка и он будет аварийно завершен до вызова close()? Этот файл теоретически может оставаться открытым гораздо дольше, чем необходимо. Пока вы ведете отладку на локальном компьютере, это не имеет большого значения. На рабочем сервере, возможно, значение всё-таки есть.

В Python 2 было для этого решение: блок try..finally. Оно всё еще работает в Python 3, и вы можете увидеть его в коде других людей или в более старом коде, который был портирован на Python 3. Но Python 2.6 представил более чистое решение, которое теперь является предпочтительным в Python 3: оператор with.

with open('examples/chinese.txt', encoding='utf-8') as a_file:
    a_file.seek(17)
    a_character = a_file.read(1)
    print(a_character)

Этот код вызывает open(), но никогда не вызывает a_file.close(). Оператор with начинает блок кода, как оператор if или цикл for. Внутри этого блока кода вы можете использовать переменную a_file в качестве потокового объекта, возвращаемого при вызове open(). Все обычные методы потокового объекта потока доступны – seek(), read(), всё, что вам нужно. Когда блок with заканчивается, Python автоматически вызывает a_file.close().

В чем особенность: независимо от того, как или когда вы выходите из блока with, Python закроет этот файл… даже если вы «выйдете» из него через необработанное исключение. Это верно, даже если ваш код вызывает исключение, и вся ваша программа останавливается, файл будет закрыт. Гарантировано.

В техническом плане оператор with создает контекст выполнения. В этих примерах потоковый объект действует как менеджер контекста. Python создает потоковый объект a_file и сообщает ему, что он входит в контекст выполнения. Когда блок кода with завершается, Python сообщает потоковому объекту, что он выходит из контекста выполнения, а потоковый объект вызывает свой собственный метод close(). Для получения подробной информации смотрите приложение B, «Классы, которые можно использовать в блоке with».

В выражении with нет ничего специфичного для файлов; это просто общая структура для создания контекстов выполнения и сообщения объектам, что они входят и выходят из контекста выполнения. Если рассматриваемый объект является потоковым объектом, то он выполняет полезные для файлов вещи (например, автоматически закрывает файл). Но это поведение определяется в потоковом объекте, а не в операторе with. Есть много других способов использовать менеджеры контекста, которые не имеют ничего общего с файлами. Вы даже можете создать свой собственный менеджер, как вы увидите позже в этой же главе.

11.2.6 Чтение данных по одной строке за раз

«Строка» текстового файла – это именно то, о чем вы подумали; например, вы ввели несколько слов и нажили клавишу Enter, и теперь вы находитесь на новой строке. Строка текста – это последовательность символов, разделенных... чем именно? Ну, это сложно, потому что, чтобы обозначить конец строки, текстовые файлы могут использовать несколько разных символов. Каждая операционная система имеет свое собственное соглашение. Некоторые используют символ возврата каретки, другие используют символ перевода строки, а некоторые используют оба этих символа в конце каждой строки.

Теперь вздохните с облегчением, потому что Python по умолчанию автоматически обрабатывает окончания строк. Если вы скажете: «Я хочу читать этот текстовый файл по одной строке за раз», Python выяснит, какой тип конца строки используется в этом текстовом файле, и всё будет просто работать.

Если вам нужен детальный контроль над тем, что считается окончанием строки, вы можете передать в функцию open() необязательный параметр newline. Для более подробной информации смотрите документацию функции open().

Итак, как на самом деле это сделать? То есть прочитать файл построчно. Это так просто, что даже красиво.

Скачать файл oneline.py.

line_number = 0
with open('examples/favorite-people.txt', encoding='utf-8') as a_file:  ①
    for a_line in a_file:                                               ②
        line_number += 1
        print('{:>4} {}'.format(line_number, a_line.rstrip()))          ③
  1. Строка 2. Используя шаблон with, вы безопасно открываете файл и позволяете Python закрыть его за вас.
  2. Строка 3. Чтобы прочитать файл по одной строке за раз, используйте цикл for. Вот оно. Помимо явных методов, таких как read(), потоковый объект также является итератором, который выдает одну строку каждый раз, когда вы запрашиваете значение.
  3. Строка 5. Используя метод строки format(), вы можете напечатать номер строки и саму строку. Спецификатор формата {:>4} означает «вывести этот аргумент с выравниванием вправо в пределах 4 пробелов». Переменная a_line содержит всю строку, символ возврата каретки и всё. Строковый метод rstrip() удаляет завершающие пробельные символы, включая символы возврата каретки.
you@localhost:~/diveintopython3$ python3 examples/oneline.py
   1 Dora
   2 Ethan
   3 Wesley
   4 John
   5 Anne
   6 Mike
   7 Chris
   8 Sarah
   9 Alex
  10 Lizzie

Получили такую ошибку?

you@localhost:~/diveintopython3$ python3 examples/oneline.py
Traceback (most recent call last):
  File "examples/oneline.py", line 4, in <module>
    print('{:>4} {}'.format(line_number, a_line.rstrip()))
ValueError: zero length field name in format

Если это так, вы, вероятно, используете Python 3.0, и вам необходимо обновиться до Python 3.1.

Python 3.0 поддерживает форматирование строк, но только с явно пронумерованными спецификаторами формата. Python 3.1 позволяет вам опустить индексы аргументов в спецификаторах формата. Вот для сравнения версия совместимая с Python 3.0:

print('{0:>4} {1}'.format(line_number, a_line.rstrip()))

11.3 Запись в текстовые файлы

Просто откройте файл и начните писать.

Вы можете писать в файлы так же, как вы читаете из них. Сначала вы открываете файл и получаете потоковый объект, затем используете методы потокового объекта для записи данных в файл, а затем закрываете файл.

Чтобы открыть файл для записи, используйте функцию open() с указанием режима записи. Существует два режима записи:

  • Режим «Запись» (write) перезапишет файл. Для выбора этого режима передайте mode='w' в функцию open().
  • Режим «Добавление» (append) добавит данные в конец файла. Для выбора этого режима передайте mode='a' в функцию open().

Любой из этих режимов, если файл еще не существует, создаст его автоматически, поэтому вам не требуется выполнять неудобную проверку типа: «если файл еще не существует, создать новый пустой файл, чтобы можно было открыть его в первый раз». Просто откройте файл и начните писать.

Как только вы заканчиваете запись в файл, вы должны всегда его закрывать, чтобы освободить дескриптор файла и убедиться, что данные действительно записаны на диск. Как и при чтении данных из файла, вы можете вызвать метод close() потокового объекта или использовать оператор with и позволить Python закрыть файл за вас. Могу поспорить, вы можете угадать, какой способ я рекомендую.

>>> with open('test.log', mode='w', encoding='utf-8') as a_file:  ①
...     a_file.write('test succeeded')                            ②
>>> with open('test.log', encoding='utf-8') as a_file:
...     print(a_file.read())                              
test succeeded
>>> with open('test.log', mode='a', encoding='utf-8') as a_file:  ③
...     a_file.write('and again')
>>> with open('test.log', encoding='utf-8') as a_file:
...     print(a_file.read())                              
test succeededand again                                           ④
  1. Строка 1. Смело начинаем, создавая новый файл test.log (или перезаписывая существующий файл) и открывая файл для записи. Параметр mode='w' означает открыть файл для записи. Да, это так же опасно, как кажется. Надеюсь, вам было наплевать на предыдущее содержимое этого файла (если оно было), потому что сейчас эти данные исчезли.
  2. Строка 2. Вы можете добавить данные во вновь открытый файл с помощью метода write() потокового объекта, возвращаемого функцией open(). После окончания блока with Python автоматически закрывает файл.
  3. Строка 6. Это было так весело, давай сделаем это снова. Но на этот раз, с mode='a', чтобы вместо перезаписи добавить данные в файл. Добавление никогда не повредит существующему содержимому файла.
  4. Строка 10. Теперь в файле test.log находятся и первая строка, которую вы записали, и вторая строка, которую вы добавили. Также обратите внимание, что в вывод не включены ни возврат каретки, ни перевод строки. Поскольку вы не записали в файл их явно, то файл не содержит их. Вы можете записать возврат каретки с помощью '\r' и/или перевод строки с помощью '\n'. Поскольку мы ничего этого не делали, всё, что мы записали в файл, оказалось в одной строке.

11.3.1 Снова про кодировку символов

Вы обратили внимание на параметр encoding, который был передан в функцию open(), когда мы открывали файл для записи? Он важен; никогда не пропускайте его! Как вы видели в начале данной главы, файлы не содержат строк, они содержат байты. Чтение «строки» из текстового файла работает только потому, что вы сообщили Python, какую кодировку использовать для чтения потока байтов и преобразования его в строку. Запись текста в файл представляет ту же задачу, но в обратном порядке. Вы не можете писать символы в файл; символы являются абстракцией. Для записи в файл Python должен знать, как преобразовать вашу строку в последовательность байтов. Единственный способ убедиться, что он выполняет правильное преобразование, – указать параметр encoding при открытии файла для записи.

11.4 Двоичные файлы

Не все файлы содержат текст. Некоторые из них содержат изображения моей собаки.

Моя собака Борегар
Моя собака Борегар
>>> an_image = open('examples/beauregard.jpg', mode='rb')                ①
>>> an_image.mode                                                        ②
'rb'
>>> an_image.name                                                        ③
'examples/beauregard.jpg'
>>> an_image.encoding                                                    ④
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: '_io.BufferedReader' object has no attribute 'encoding'
  1. Строка 1. Открыть файл в двоичном режиме просто, но при этом есть некоторые тонкости. Единственное отличие от открытия в текстовом режиме заключается в том, что параметр mode содержит символ 'b'.
  2. Строка 2. Потоковый объект, который вы получаете при открытии файла в двоичном режиме, имеет много таких же атрибутов, включая mode, который отражает параметр mode, который вы передали в функцию open().
  3. Строка 4. У двоичных потоковых объектов так же, как и текстовых потоковых объектов, есть атрибут name.
  4. Строка 6. Здесь есть одно отличие: двоичный потоковый объект не имеет атрибута encoding. В этом есть смысл, верно ведь? Вы читаете (или пишете) байты, а не строки, поэтому для Python нечего преобразовывать. То, что вы получаете из двоичного файла, – это именно то, что вы в него добавляете, преобразование не требуется.

Я упоминал, что вы читаете байты?

# продолжение предыдущего примера
>>> an_image.tell()
0
>>> data = an_image.read(3)  ①
>>> data
b'\xff\xd8\xff'
>>> type(data)               ②
<class 'bytes'>
>>> an_image.tell()          ③
3
>>> an_image.seek(0)
0
>>> data = an_image.read()
>>> len(data)
3150
  1. Строка 4. Как и текстовые файлы, вы можете читать двоичные файлы небольшими кусочками. Но есть принципиальная разница...
  2. Строка 7. … вы читаете байты, а не строки. Поскольку вы открыли файл в двоичном режиме, метод read() принимает количество считываемых байтов, а не количество символов.
  3. Строка 9. Это означает, что никогда не будет неожиданного несоответствия между числом, которое вы передали в метод read(), и индексом позиции, который вы получите из метода tell(). Метод read() читает байты, а методы seek() и tell() отслеживают количество прочитанных байтов. Для двоичных файлов они всегда сходятся.

11.5 Потоковые объекты из нефайловых источников

Представьте, что вы пишете библиотеку, и одна из ваших библиотечных функций будет считывать некоторые данные из файла. Функция может просто взять имя файла в виде строки, открыть файл для чтения, прочитать его и закрыть перед выходом. Но вы не должны это делать. Вместо этого ваш API должен принимать произвольный потоковый объект.

В простейшем случае потоковым объектом является что-либо с методом read(), который принимает необязательный параметр size и возвращает строку. При вызове без параметра size метод read() должен прочитать всё, что можно прочитать из входного источника, и вернуть все данные в виде одного значения. При вызове с параметром size этот метод считывает это указанное количество данных из входного источника и возвращает это количество данных. При повторном вызове он находит, где остановился, и возвращает следующий фрагмент данных.

Это выглядит точно так же, как потоковый объект, который вы получаете при открытии реального файла. Разница в том, что вы не ограничиваете себя настоящими файлами. Входной источник, который может быть «прочитан», может быть чем угодно: веб-страница, строка в памяти, даже выходные данные другой программы. Пока ваши функции принимают потоковый объект и просто вызывают метод read() этого объекта, вы можете обрабатывать любой входной источник, который действует как файл, без специального кода для обработки каждого вида входа.

Чтобы прочитать из поддельного файла, просто вызовите read().

>>> a_string = 'PapayaWhip is the new black.'
>>> import io                                  ①
>>> a_file = io.StringIO(a_string)             ②
>>> a_file.read()                              ③
'PapayaWhip is the new black.'
>>> a_file.read()                              ④
''
>>> a_file.seek(0)                             ⑤
0
>>> a_file.read(10)                            ⑥
'PapayaWhip'
>>> a_file.tell()                       
10
>>> a_file.seek(18)
18
>>> a_file.read()
'new black.'
  1. Строка 2. Модуль io определяет класс StringIO, который можно использовать для обработки строки в памяти как файла.
  2. Строка 3. Чтобы создать потоковый объект из строки, создайте экземпляр класса io.StringIO() и передайте ему строку, которую вы хотите использовать в качестве «файловых» данных. Теперь у вас есть потоковый объект, и вы можете делать с ним всё, что можно делать с потоками.
  3. Строка 4. Вызов метода read() «читает» весь «файл», который в случае объекта StringIO просто возвращает исходную строку.
  4. Строка 6. Как и с реальным файлом, повторный вызов метода read() возвращает пустую строку.
  5. Строка 8. Вы можете явно искать начало строки, точно так же как в реальном файле, используя метод seek() объекта StringIO.
  6. Строка 10. Вы также можете прочитать строку фрагментами, передав методу read() параметр size.

io.StringIO позволяет вам рассматривать строку как текстовый файл. Существует также класс io.BytesIO, который позволяет обрабатывать байтовый массив как двоичный файл.

11.5.1 Обработка сжатых файлов

Стандартная библиотека Python содержит модули, которые поддерживают чтение и запись сжатых файлов. Есть несколько различных методов сжатия; два наиболее популярных в системах, отличных от Windows, это gzip и bzip2. (Возможно, вы также сталкивались с архивами PKZIP и GNU Tar. В Python есть модули и для них.)

Модуль gzip позволяет создавать потоковый объект для чтения или записи сжатого gzip файла. Предоставляемый потоковый объект поддерживает метод read() (если вы открыли его для чтения) и метод write() (если вы открыли его для записи). Это означает, что вы можете использовать методы, которые вы уже изучили для обычных файлов, для непосредственного чтения или записи сжатого gzip файла без создания временного файла для хранения распакованных данных.

В качестве дополнительного бонуса он также поддерживает оператор with, поэтому вы можете позволить Python автоматически закрывать сжатый gzip файл, когда вы закончите работу с ним.

you@localhost:~$ python3

>>> import gzip
>>> with gzip.open('out.log.gz', mode='wb') as z_file:                                      ①
...   z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8'))
... 
>>> exit()

you@localhost:~$ ls -l out.log.gz                                                           ②
-rw-r--r--  1 mark mark    79 2009-07-19 14:29 out.log.gz
you@localhost:~$ gunzip out.log.gz                                                          ③
you@localhost:~$ cat out.log                                                                ④
A nine mile walk is no joke, especially in the rain.
  1. Строка 4. Открывать сжатые файлы всегда необходимо в двоичном режиме (обратите внимание на символ 'b' в аргументе mode).
  2. Строка 9. Этот пример я писал на Linux. Если вы не знакомы с командной строкой, эта команда показывает «длинный список» сжатого gzip файла, который мы только что создали в оболочке Python. Этот листинг показывает, что файл существует (уже хорошо) и его длина составляет 79 байт. Это на самом деле больше, чем строка, с которой мы начали! Формат gzip-файла включает в себя заголовок фиксированной длины, который содержит некоторые метаданные о файле, поэтому он неэффективен для очень маленьких файлов.
  3. Строка 11. Команда gunzip (произносится как «gee-unzip») распаковывает файл и сохраняет его содержимое в новом файле, названном так же, как сжатый файл, но без расширения .gz.
  4. Строка 12. Команда cat отображает содержимое файла. Этот файл содержит строку, которую мы изначально записали из оболочки Python непосредственно в сжатый файл out.log.gz.

Получили такую ошибку?

>>> with gzip.open('out.log.gz', mode='wb') as z_file:
...         z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8'))
... 
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'GzipFile' object has no attribute '__exit__'

Если так, то вы, вероятно, используете Python 3.0, и вам необходимо обновиться до Python 3.1.

В Python 3.0 был модуль gzip, но он не поддерживал использование объекта сжатого gzip файла в качестве менеджера контекста. В Python 3.1 добавлена ​​возможность использования объектов сжатых gzip файлов в операторе with.

11.6 Стандартные ввод, вывод и вывод ошибок

sys.stdin, sys.stdout, sys.stderr.

Гуру командной строки уже знакомы с концепцией стандартного ввода, стандартного вывода и стандартного вывода ошибок. Этот раздел для остальных из вас.

Стандартный вывод (standard output) и стандартный вывод ошибок (standard error) (обычно сокращенно stdout и stderr) – это каналы, которые встроены в каждую UNIX-подобную систему, включая Mac OS X и Linux. Когда вы вызываете функцию print(), то, что вы печатаете, отправляется в канал stdout. Когда ваша программа дает сбой и распечатывает трассировку, та отправляется в канал stderr. По умолчанию оба эти канала просто подключены к окну терминала, с которым вы работаете; когда ваша программа что-то печатает, вы видите этот вывод в окне терминала, а когда программа аварийно завершается, в окне терминала вы видите трассировку. В графической оболочке Python для каналов stdout и stderr по умолчанию используется «интерактивное окно».

>>> for i in range(3):
...     print('PapayaWhip')                ①
PapayaWhip
PapayaWhip
PapayaWhip
>>> import sys
>>> for i in range(3):
...     l = sys.stdout.write('is the')     ②
is theis theis the
>>> for i in range(3):
...     l = sys.stderr.write('new black')  ③
new blacknew blacknew black
  1. Строка 2. Функция print() в цикле. Здесь нет ничего неожиданного.
  2. Строка 8.stdout определен в модуле sys и является потоковым объектом. Вызов его функции write() распечатает любую строку, которую вы ей дадите, а затем вернет длину вывода. Фактически, это то, что на самом деле делает функция print(); она добавляет в конец печатаемой строки символ возврата каретки и вызывает sys.stdout.write.
  3. Строка 11. В простейшем случае sys.stdout и sys.stderr отправляют свои выходные данные в одно и то же место: Python IDE (если вы в нем) или в терминал (если вы запускаете Python из командной строки). Как и стандартный вывод, стандартный вывод ошибок не добавляет за вас символ возврата каретки. Если он вам нужен, то вы должны сами вывести его в печать.

sys.stdout и sys.stderr являются потоковыми объектами, но они доступны только для записи. Попытки вызвать их метод read() всегда вызывает исключение IOError.

>>> import sys
>>> sys.stdout.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: not readable

11.6.1 Перенаправление стандартного вывода

sys.stdout и sys.stderr являются потоковыми объектами, хотя и поддерживают только запись. Но они не константы; они переменные. Это означает, что вы можете назначить им новое значение (любой другой потоковый объект), чтобы перенаправить их вывод.

пропустить этот список кода

Скачать файл stdout.py.

import sys

class RedirectStdoutTo:
    def __init__(self, out_new):
        self.out_new = out_new

    def __enter__(self):
        self.out_old = sys.stdout
        sys.stdout = self.out_new

    def __exit__(self, *args):
        sys.stdout = self.out_old

print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
    print('B')
print('C')

Проверь этот вывод:

you@localhost:~/diveintopython3/examples$ python3 stdout.py
A
C
you@localhost:~/diveintopython3/examples$ cat out.log
B

Получили такую ошибку?

you@localhost:~/diveintopython3/examples$ python3 stdout.py
  File "stdout.py", line 15
    with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
                                                              ^
SyntaxError: invalid syntax

Если это так, вы, вероятно, используете Python 3.0, и вам необходимо обновиться до Python 3.1.

Python 3.0 поддерживает оператор with, но каждый оператор может использовать только один менеджер контекста. Python 3.1 позволяет объединять несколько менеджеров контекста в одном операторе with.

Давайте сначала возьмем последний фрагмент.

print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
    print('B')
print('C')

В нем используется усложненный оператор with. Давайте перепишем этот фрагмент как нечто более узнаваемое.

with open('out.log', mode='w', encoding='utf-8') as a_file:
    with RedirectStdoutTo(a_file):
        print('B')

Как показало переписывание, у нас есть два оператора with, один из которых вложен в область видимости другого. «Внешний» оператор with должен быть вам уже знаком: он открывает для записи текстовый файл в кодировке UTF-8 с именем out.log и назначает потоковый объект переменной с именем a_file. Но дальше идет что-то странное.

with RedirectStdoutTo(a_file):

Где условие as? Оператор with на самом деле его не требует. Так же, как вы можете вызывать функцию и игнорировать возвращаемое ею значение, вы можете иметь оператор with, который не присваивает переменной контекст with. В этом случае вас интересуют только побочные эффекты контекста RedirectStdoutTo.

Каковы эти побочные эффекты? Загляните внутрь класса RedirectStdoutTo. Этот класс является пользовательским менеджером контекста. Менеджером контекста может стать любой класс, определив два специальных метода: __enter__() и __exit__().

class RedirectStdoutTo:
    def __init__(self, out_new):    ①
        self.out_new = out_new

    def __enter__(self):            ②
        self.out_old = sys.stdout
        sys.stdout = self.out_new

    def __exit__(self, *args):      ③
        sys.stdout = self.out_old
  1. Строка 2. Метод __init__() вызывается сразу после создания экземпляра. Он принимает один параметр, потоковый объект, который вы хотите использовать в качестве стандартного вывода во время жизни контекста. Этот метод просто сохраняет потоковый объект в переменной экземпляра, чтобы другие методы могли использовать его позже.
  2. Строка 5. Метод __enter__() – это специальный метод класса; Python вызывает его при входе в контекст (т.е. в начале оператора with). Этот метод сохраняет текущее значение sys.stdout в self.out_old, затем перенаправляет стандартный вывод, присваивая self.out_new переменной sys.stdout.
  3. Строка 9. Метод __exit__() – это еще один специальный метод класса; Python вызывает его при выходе из контекста (то есть в конце оператора with). Этот метод восстанавливает стандартный вывод в его исходное значение, присваивая сохраненное значение self.out_old переменой sys.stdout.

Объединим всё это вместе:

print('A')                                                                             ①
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):  ②
    print('B')                                                                         ③
print('C')                                                                             ④
  1. Строка 1. Распечатать в «интерактивном окне» IDE (или в терминале, если скрипт запущен из командной строки).
  2. Строка 2. Этот оператор with принимает список контекстов, разделенный запятыми. Список, разделенный запятыми, действует как последовательность вложенных блоков. Первый перечисленный контекст – это «внешний» блок; последний – «внутренний» блок. Первый контекст открывает файл; второй контекст перенаправляет sys.stdout в потоковый объект, который был создан в первом контексте.
  3. Строка 3. Поскольку эта функция print() выполняется в контексте, созданном оператором with, она не будет выводить текст на экран; она запишет этот текст в файл out.log.
  4. Строка 4. Блок кода with завершен. Python указал каждому менеджеру контекста выполнить то, что они делают при выходе из контекста. Менеджеры контекста формируют стек «последним пришел – первым вышел». При выходе второй контекст вернул sys.stdout к его исходному значению, затем первый контекст закрыл файл с именем out.log. Поскольку стандартный вывод был восстановлен до исходного значения, вызов функции print() снова выведет текст на экран.

Перенаправление стандартного вывода ошибок работает точно так же, при этом вместо sys.stdout используется sys.stderr.

11.7 Материалы для дальнейшего чтения

Источник:

  • Mark Pilgrim. Dive Into Python 3

Теги

PythonВысокоуровневые языки программированияОбучениеПрограммированиеЯзыки программирования

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.