Как устроен Python. Как устроен Python. Харрисон. Харрисон Мэтт
Скачать 5.41 Mb.
|
19 Операции ввода/вывода с файлами Операции чтения и записи файлов часто встречаются в программирова- нии. Python в значительной мере упрощает выполнение этих операций. Вы можете читать как текстовые, так и двоичные файлы; чтение может осуществляться по байтам, строкам и даже по всему содержимому файла. Аналогичные возможности доступны и для записи файлов. 19.1. Открытие файлов Функция Python open возвращает объект файла. Эта функция может получать несколько необязательных параметров: open(filename, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) ПОДСКАЗКА В системе Windows могут возникнуть проблемы из-за символа \ , использу- емого в качестве разделителя путей. Строки Python также используют \ как экранирующий символ. Если у вас имеется каталог с именем test и вы исполь- зуете запись "C:\test" , Python интерпретирует \t как символ табуляции. Проблема решается использованием необработанных строк для представле- ния путей Windows. Поставьте символ r перед строкой: r"C:\test" 170 Глава 19. Операции ввода/вывода с файлами Обычно интерес представляют только первые два или три параметра. Первый параметр — имя файла — является обязательным. Второй па- раметр определяет, какая операция выполняется с файлом (чтение или запись) и является файл текстовым или двоичным. Режимы перечислены в таблице на рис. 19.1. Режим Описание 'r' Чтение текстового файла (используется по умолчанию) 'w' Запись текстового файла (перезапись, если файл существует) 'x' Запись текстового файла (исключение FileExistsError , если файл существует) 'a' Присоединение к текстовому файлу (запись в конец) 'rb ' Чтение двоичного файла 'wb' Запись двоичного файла (перезапись) 'w+b' Открытие двоичного файла для чтения и записи 'xb' Запись двоичного файла (исключение FileExistsError , если файл существует) 'ab' Присоединение к двоичному файлу (запись в конец) Рис. 19.1. Режимы работы с файлами Чтобы получить подробную информацию о параметрах функции open , вызовите для нее help . Эта функция содержит чрезвычайно подробную документацию. Если вы работаете с текстовыми файлами, обычно в параметре themode передается 'r' для чтения файла или 'w' для записи. В следующем при- мере файл /tmp/a.txt открывается для записи. Python создаст этот файл или заменит его, если он уже существует: >>> a_file = open('/tmp/a.txt', 'w') Объект файла, возвращаемый при вызове open , содержит различные ме- тоды для чтения и записи. В этой главе рассматриваются наиболее часто используемые методы. Чтобы ознакомиться с полной документацией по объекту файла, передайте его при вызове функции help . Функция вы- ведет список всех методов и покажет, что они делают. 19.2. Чтение текстовых файлов 171 ПРИМЕЧАНИЕ В системах UNIX временные файлы хранятся в каталоге /tmp . Если вы рабо- таете в Windows, просмотрите переменную среды Temp следующей командой: c:\> ECHO %Temp% Обычно результат выглядит так: C:\Users\ 19.2. Чтение текстовых файлов Python предоставляет разные способы чтения данных из файлов. Открыв файл в текстовом режиме (используется по умолчанию), вы сможете читать или записывать в него строки. Если же файл открыт в двоичном режиме ( 'rb' для чтения, 'wb' для записи), то вы будете читать и запи- сывать байтовые строки. Для чтения отдельной строки из существующего текстового файла ис- пользуется метод .readline . Если режим не задан, Python по умолчанию использует режим чтения текстового файла (режим r ): >>> passwd_file = open('/etc/passwd') >>> passwd_file.readline() 'root:x:0:0:root:/root:/bin/bash' Будьте осторожны — при попытке открыть для чтения несуществующий файл Python выдает ошибку: >>> fin = open('bad_file') Traceback (most recent call last): IOError: [Errno 2] No such file or directory: 'bad_file' СОВЕТ Функция open возвращает экземпляр объекта файла. У этого объекта есть методы для чтения и записи данных. Стандартные имена переменных для объектов файлов — fin (file input), fout (file output), fp (file pointer — используется для ввода или вывода), 172 Глава 19. Операции ввода/вывода с файлами или такие имена, как passwd_file . Такие имена, как fin и fout , удобны тем, что они показывают, что файл используется для чтения или записи соответственно. Как показано выше, метод .readline возвращает одну строку файла. Вы можете многократно вызывать его для получения каждой строки или же воспользоваться методом .readlines для получения списка, содержащего все строки. Чтобы прочитать все содержимое файла в одну строку, используйте метод .read : >>> passwd_file = open('/etc/passwd') >>> print(passwd_file.read()) root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/bin/false daemon:x:2:2:daemon:/sbin:/bin/false adm:x:3:4:adm:/var/adm:/bin/false Всегда закрывайте файлы после завершения работы с ними; для этого следует вызвать метод .close : >>> passwd_file.close() Как видите, закрыть файл совсем несложно. А немного позднее вы пой- мете, почему это так важно. 19.3. Чтение двоичных файлов Чтобы прочитать данные из двоичного файла, передайте в themode стро- ку 'rb' (Read Binary). При чтении данных из двоичного файла вы полу- чаете не обычные строки, а байтовые строки. Не беспокойтесь, интерфейс для работы с байтовыми строками очень похож на интерфейс работы с обычными строками. Ниже приведены первые 8 байтов PNG-файла. Чтобы прочитать 8 байтов, передайте значение 8 методу .read : >>> bfin = open('img/dict.png', 'rb') >>> bfin.read(8) b'\x89PNG\r\n\x1a\n' 19.4. Перебор при работе с файлами 173 Обратите внимание на b в начале строки — это признак байтовой строки. Вы также можете вызвать .readline для двоичного файла; метод будет читать данные до тех пор, пока не достигнет b'\n' . Сравнение двоичных и обычных строк будет представлено в одной из следующих глав. 19.4. Перебор при работе с файлами Перебор в последовательностях уже упоминался ранее. В языке Python вы можете легко провести перебор по строкам файла. При работе с тек- стовым файлом можно провести перебор по методу .readlines для того, чтобы получать строки одну за одной: >>> fin = open('/etc/passwd') >>> for line in fin.readlines(): ... print(line) Но поскольку .readlines возвращает список, Python придется прочитать весь файл для создания списка, а это может создать проблемы. Если, допустим, файл содержит записи журнала на сервере, он может израс- ходовать всю свободную память. Впрочем, у Python для таких случаев имеется один прием: для перебора строк файла Python позволяет про- вести перебор по экземпляру файла. При прямом переборе по файлу Python не выполняет работу заранее и читает строки текста только по мере надобности: >>> fin = open('/etc/passwd') >>> for line in fin: ... print(line) Как происходит перебор прямо по экземпляру файла? У Python име- ется специальный метод .__iter__ , который определяет поведение для перебора по экземпляру. Для класса файла метод .__iter__ перебирает строки файла. Метод .readlines следует применять только в том случае, если вы уве- рены, что файл поместится в памяти, а вам потребуется обращаться к строкам неоднократно. В противном случае прямой перебор по файлу является предпочтительным. 174 Глава 19. Операции ввода/вывода с файлами 19.5. Запись файлов Чтобы записать данные в файл, необходимо сначала открыть файл в ре- жиме записи. Если выбрать режим 'w' , файл будет открыт для записи текстовых данных: >>> fout = open('/tmp/names.txt', 'w') >>> fout.write('George') Этот вызов попытается перезаписать файл /tmp/names.txt в том случае, если он существует; в противном случае файл будет создан. Если у вас нет разрешения для обращения к файлу, выдается ошибка Permission Error Если путь указан некорректно, вы получите ошибку FileNotFoundError Для размещения данных в файле используются два метода — .write и .writelines . Метод .write получает строку в параметре и записывает данные в файл. Метод .writelines получает последовательность со стро- ковыми данными и записывает ее в файл. ПРИМЕЧАНИЕ Чтобы включить в файл символы новой строки, вы должны явно пере- дать их методам файла. На платформах UNIX строки, передаваемые .write , должны завершаться комбинацией \n . Аналогичным образом все строки последовательности, передаваемой .writelines , тоже должны завершаться \n . В системе Windows признаком новой строки служит ком- бинация \r\n Чтобы вы могли писать кроссплатформенный код, строка linesep из модуля os определяет правильную комбинацию новой строки для платформы: >>> import os >>> os.linesep # Платформа UNIX '\n' СОВЕТ Если вы опробовали приведенный выше пример в интерпретаторе, возможно, вы заметили, что файл /tmp/names.txt остался пустым, хотя вы и приказали Python записать в него имя «George». Что произошло? 19.6. Закрытие файлов 175 Файловый вывод буферизуется операционной системой. Чтобы оптимизи- ровать запись на носители информации, операционная система записывает данные только при достижении некоторого порога. В системах Linux этот порог обычно составляет 4 Кбайт. Чтобы осуществить принудительную запись данных, вызовите метод .flush , который сбрасывает незаписанные данные на носитель информации. Более радикальный механизм, гарантирующий, что данные будут записаны, основан на вызове метода .close . Вызов этого метода сообщает Python, что запись в файл завершена: >>> fout2 = open('/tmp/names2.txt', ... 'w') >>> fout2.write('John\n') >>> fout2.close() 19.6. Закрытие файлов Как упоминалось ранее, вызов .close записывает файловые буферы на носитель информации. Правила Python требуют всегда закрывать фай- лы после завершения работы с ними (как для чтения, так и для записи). Есть несколько причин, по которым файлы должны явно закрываться в программах: Если файл хранится в глобальной переменной, он никогда не будет автоматически закрыт во время выполнения программы. cPython автоматически закрывает файлы, если они уничтожаются в ходе уборки мусора. Другие реализации Python могут этого не де- лать. Без вызова .flush вы не знаете, когда ваши данные будут записаны. Вероятно, в вашей операционной системе установлено ограничение количества открытых файлов на процесс. Некоторые операционные системы не позволяют удалить открытый файл. 176 Глава 19. Операции ввода/вывода с файлами Python обычно берет на себя уборку мусора, и программисту не при- ходится беспокоиться об уничтожении объектов. Операции открытия и закрытия файлов являются исключениями. Python автоматически закрывает файл при выходе объекта файла из области видимости. Тем не менее в этом случае не стоит полагаться на уборку мусора. Явно выра- жайте свои намерения и прибирайте за собой — обязательно закрывайте файлы! В Python 2.5 появилась команда with . Она используется в сочетании с менеджерами контекста для контроля условий, которые происходят до и после выполнения блока. Функция open также служит менеджером контекста для обеспечения того, что файл будет открыт перед входом в блок и будет закрыт при выходе из блока. Пример: >>> with open('/tmp/names3.txt', 'w') as fout3: ... fout3.write('Ringo\n') Этот фрагмент эквивалентен следующему: >>> fout3 = open('/tmp/names3.txt', 'w') >>> fout3.write('Ringo\n') >>> fout3.close() Обратите внимание на то, что строка с with завершается двоеточием. Ког- да строка кода Python завершается двоеточием, последующий код всегда снабжается отступом. Код с отступом, следующий за двоеточием, назы- вается блоком, или телом, менеджера контекста. В следующем примере блок состоит из операции записи текста Ringo в файл. После этого блок завершается. В приведенном примере этого не видно, но о завершении блока with можно судить по тому, что у кода пропадает отступ. В этот момент менеджер контекста вступает в действие и выполняет логику выхода. Логика выхода файлового менеджера контекста приказывает Python автоматически закрыть файл при завершении блока. СОВЕТ Используйте конструкцию with для чтения и записи файлов. Закрывать файлы — полезная привычка, и если вы используете команду with при рабо- те с файлами, вам не придется беспокоиться об их закрытии. Команда with автоматически закрывает файл за вас. 19.7. Проектирование на основе файлов 177 19.7. Проектирование на основе файлов Вы уже видели, как использовать функции для организации и структу- рирования сложных программ. Одно из преимуществ использования функций заключается в том, что функции могут повторно использоваться в коде. Приведем полезный совет по организации функций, работающих с файлами. Предположим, вы хотите написать код, который получает имя файла и создает последовательность строк из файла, причем перед каждой строкой вставляется ее номер. На первый взгляд может показаться, что API (программный интерфейс, Application Programming Interface) ваших функций должен получать имя файла, содержимое которого вы хотите изменить: >>> def add_numbers(filename): ... results = [] ... with open(filename) as fin: ... for num, line in enumerate(fin): ... results.append( ... '{0}-{1}'.format(num, line)) ... return results Такой код нормально работает. Но что произойдет, если потребуется вставить номера перед строками, взятыми не из файла, а из другого ис- точника? Если потребуется протестировать код, теперь придется обра- щаться к файловой системе. Одно из решений — рефакторинг функции add_numbers , чтобы она только открывала файл в менеджере контекста, а затем вызывала другую функцию — add_nums_to_seq . Новая функция содержит логику, которая работает с последовательностью, а не зависит от имени файла. Так как файл ведет себя как последовательность строк, исходная функциональность будет сохранена: >>> def add_numbers(filename): ... with open(filename) as fin: ... return add_nums_to_seq(fin) >>> def add_nums_to_seq(seq): ... results = [] ... for num, line in enumerate(seq): ... results.append( 178 Глава 19. Операции ввода/вывода с файлами ... '{0}-{1}'.format(num, line)) ... return results Теперь у вас имеется более общая функция add_nums_to_seq , более про- стая для тестирования и повторного использования, потому что вместо зависимости от имени файла она зависит от последовательности. Вы можете передать список строк или создать фиктивный файл, который будет передаваться функции. ПОДСКАЗКА Существуют и другие типы, которые тоже реализуют интерфейс, сходный с интерфейсом файлов (чтение и запись). В любой момент, когда вам при- ходится использовать имя файла при программировании, спросите себя, можно ли применить эту логику к другим видам последовательностей. Если это возможно, используйте приведенный выше пример рефакторинга для получения кода, который создает меньше проблем с повторным использо- ванием и тестированием. 19.8. Итоги Python предоставляет единую функцию open для взаимодействия как с текстовыми, так и с двоичными файлами. Указывая режим, вы сооб- щаете Python, какие операции должны выполняться с файлом — чтение или запись. При работе с текстовыми файлами читаются и записываются строки. При работе с двоичными файлами читаются и записываются байтовые строки. Не забывайте закрывать файлы. Идиоматическим способом закрытия считается использование команды with . Наконец, следите за тем, чтобы ваши функции могли работать с последовательностями данных вместо имен файлов, так как это сделает ваш код более универсальным. 19.9. Упражнения 1. Напишите функцию для записи файлов данных, разделенных за- пятыми (CSV, Comma Separated Values). Функция должна полу- чать в параметрах имя файла и список кортежей. Кортежи долж- ны содержать имя, адрес и возраст. Файл должен создать строку 19.9. Упражнения 179 заголовка, за которой следует строка для каждого кортежа. Если функции будет передан следующий список кортежей: [('George', '4312 Abbey Road', 22), ('John', '54 Love Ave', 21)] в файл должны быть записаны следующие данные: name,address,age George,4312 Abbey Road,22 John,54 Love Ave,21 2. Напишите функцию для чтения CSV-файлов. Она должна возвра- щать список словарей, интерпретируя первую строку как имена ключей, а каждую последующую строку как значения этих ключей. Для данных в приведенном примере будет возвращен следующий результат: [{'name': 'George', 'address': '4312 Abbey Road', 'age': 22}, {'name': 'John', 'address': '54 Love Ave', 'age': 21}] 20 Юникод Строки уже неоднократно встречались вам в этой книге, но мы еще не обсуждали одно из самых больших изменений Python 3 — строки Юни- кода. В Python 2 строки Юникода поддерживались, но их нужно было специально создавать. Теперь ситуация изменилась, все строки хранятся в Юникоде. 20.1. Историческая справка Что такое Юникод (Unicode)? Это стандарт представления глифов (символы, входящие в большинство письменных языков, а также знаки и эмодзи). Спецификацию стандарта можно найти на сайте Юникода 1 , причем стандарт часто обновляется. Стандарт состоит из различных до- кументов и диаграмм, связывающих кодовые пункты (шестнадцатерич- ные числа, такие как 0048 или 1F600) с глифами (H или ☺ ) и именами ( LATIN CAPITAL H и GRINNING FACE ). Кодовые пункты и имена уникальны, хотя многие глифы кажутся очень похожими. А теперь небольшая историческая справка. С широким распростране- нием компьютеров разные организации стали предлагать разные схемы для отображения двоичных данных на строковые данные. Одна из та- ких кодировок — ASCII — использует 7 битов данных для отображения на 128 знаков и управляющих символов. Такая кодировка нормально работает в средах, ориентированных на латинский алфавит, — напри- 1 https://unicode.org |