У меня есть файлы, у меня есть компьютерные файлы, есть, как вы понимаете, и файлы на бумаге. Но большая часть [истории] хранится у меня в голове. Помоги мне, Господь, если что-то случится с моей головой.
Джордж Р.Р. Мартин
Когда вы только начинаете программировать, некоторые слова вы слышите раз за разом, но не уверены, имеют ли они какое-то техническое значение или просто сотрясают воздух. Термины «файл» и «каталог» входят в список таких слов, и они на самом деле имеют техническое значение. Файл — это последовательность байтов, хранящаяся в определенной файловой системе, к которой можно получить доступ по имени файла. Каталог — это коллекция файлов и, возможно, других каталогов. Слово «папка» является синонимом каталога: оно вошло в обиход, когда компьютеры получили графический пользовательский интерфейс, а чтобы вещи казались более знакомыми, использовались некоторые офисные концепции.
Многие файловые системы иерархичны и зачастую похожи на деревья. В реальных офисах, как правило, не бывает деревьев, и аналогия с папками работает только в том случае, если вы визуализируете все подкаталоги.
Самый простой вид хранения — это старый добрый файл, иногда называемый плоским файлом. Он представляет собой последовательность байтов, которая хранится под именем файла. Вы считываете данные из файла в память и записываете данные из памяти в файл. Python позволяет делать это довольно легко. Как и во многих других языках, операции с файлами, присутствующие в этом языке программирования, в большинстве своем были смоделированы на основе знакомых и популярных аналогов, имеющихся в Unix.
Вам нужно вызвать функцию open, прежде чем сделать следующее:
• прочитать существующий файл;
• записать в новый файл;
• добавить данные в существующий файл;
• перезаписать существующий файл.
файл_об = open(имя_файла, режим)
Кратко поясню фрагменты этого вызова:
•файл_об — это объект файла, возвращаемый функцией open();
• имя_файла — это строка, представляющая собой имя файла;
•режим — это строка, указывающая на тип файла и действия, которые вы хотите произвести над файлом.
Первая буква строки mode указывает на операцию:
•r означает чтение;
• w означает запись. Если файла не существует, он будет создан. Если файл существует, он будет перезаписан;
• x означает запись, но только если файла еще не существует;
•a означает добавление данных в конец файла, если он существует.
Вторая буква строки mode указывает на тип файла:
•t (или ничего) означает, что файл текстовый;
•b означает, что файл бинарный.
После открытия файла вы вызываете функции для чтения или записи данных. Они будут показаны в следующих примерах.
Наконец, вам нужно закрыть файл, чтобы гарантировать, что все операции записи завершены, а память освобождена. Далее вы узнаете, как оператор with может это автоматизировать.
В данной программе открывается файл с именем oops.txt, а затем закрывается без выполнения каких-либо действий. В результате будет создан пустой файл:
>>> fout = open('oops.txt', 'wt')
>>> fout.close()
Создадим заново файл oops.txt, но теперь запишем в него строку, а затем закроем:
>>> fout = open('oops.txt', 'wt')
>>> print('Oops, I created a file.', file=fout)
>>> fout.close()
Мы создали пустой файл oops.txt в предыдущем подразделе, поэтому программа перезаписала его. В вызове функции print мы использовали аргумент file. Без него функция print будет записывать данные в стандартный поток вывода, которым является ваша консоль (если только вы не указали программе оболочки перенаправить вывод данных в файл с помощью оператора > или не направили его в другую программу с помощью оператора |).
Мы использовали функцию print для того, чтобы записать строку в файл. Но можно использовать и функцию write.
В качестве примера многострочного источника данных возьмем лимерик о специальной теории относительности:
>>> poem = '''There was a young lady named Bright,
... Whose speed was far faster than light;
... She started one day
... In a relative way,
... And returned on the previous night.'''
>>> len(poem)
150
Следующий код записывает это стихотворение в файл 'relativity' с помощью всего одного вызова:
>>> fout = open('relativity', 'wt')
>>> fout.write(poem)
150
>>> fout.close()
Функция write() возвращает число записанных байтов. Она не добавляет никаких пробелов или символов новой строки, в отличие от функции print(). Как и раньше, вы можете записать в текстовый файл несколько строк:
>>> fout = open('relativity', 'wt')
>>> print(poem, file=fout)
>>> fout.close()
Отсюда возникает вопрос: какую функцию использовать — write() или print()? Как вы уже видели, по умолчанию функция print() добавляет пробел после каждого аргумента и символ новой строки в конце. В предыдущем примере она добавила символ новой строки в файл relativity. Для того чтобы функция print() работала как функция write(), передайте ей два следующих аргумента:
•sep (разделитель, по умолчанию это пробел '');
•end (символ конца файла, по умолчанию это символ новой строки '\n').
Вместо значений по умолчанию мы используем пустые строки:
>>> fout = open('relativity', 'wt')
>>> print(poem, file=fout, sep='', end='')
>>> fout.close()
Если исходная строка большая, вы можете записывать в файл ее фрагменты (используя разделения (слайсы)) до тех пор, пока не запишете всю:
>>> fout = open('relativity', 'wt')
>>> size = len(poem)
>>> offset = 0
>>> chunk = 100
>>> while True:
... if offset > size:
... break
... fout.write(poem[offset:offset+chunk])
... offset += chunk
...
100
50
>>> fout.close()
Этот код записал 100 символов первым заходом и оставшиеся 50 — вторым. Слайсы позволяют вам забраться за границы последовательности без генерации исключения.
Если файл relativity нам очень дорог, проверим, спасет ли режим х его от перезаписывания:
>>> fout = open('relativity', 'xt')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileExistsError: [Errno 17] File exists: 'relativity'
Вы можете использовать этот код вместе с обработчиком исключений:
>>> try:
... fout = open('relativity', 'xt')]
... fout.write('stomp stomp stomp')
... except FileExistsError:
... print('relativity already exists!. That was a close one.')
...
relativity already exists!. That was a close one.
Вы можете вызвать функцию read() без аргументов, чтобы достать весь файл сразу, как показано в следующем примере. Будьте осторожны, делая это с крупными файлами: файл размером 1 Гбайт потребит 1 Гбайт памяти:
>>> fin = open('relativity', 'rt' )
>>> poem = fin.read()
>>> fin.close()
>>> len(poem)
150
Вы можете указать максимальное количество символов, которое функция read() вернет за один вызов. Мы будем считывать по 100 символов за раз и присоединять каждый фрагмент к строке poem, чтобы восстановить оригинал:
>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> chunk = 100
>>> while True:
... fragment = fin.read(chunk)
... if not fragment:
... break
... poem += fragment
...
>>> fin.close()
>>> len(poem)
150
После того как вы считаете весь файл, дальнейшие вызовы функции read() будут возвращать пустую строку '', которая рассматривается как False при проверке ifnotfragment. Это позволит выйти из цикла whileTrue.
Вы также можете считывать файл построчно с помощью функции readline(). В следующем примере мы будем присоединять по одной строке к poem, чтобы восстановить оригинал:
>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> while True:
... line = fin.readline()
... if not line:
... break
... poem += line
...
>>> fin.close()
>>> len(poem)
150
Для текстового файла даже пустая строка имеет длину, равную 1 (символ новой строки), такая строка будет считаться True. Когда весь файл будет считан, функция readline() (как и функция read()) возвратит пустую строку, которая будет считаться False.
Самый простой способ считать текстовый файл — использовать итератор. Он будет возвращать по одной строке за раз. Этот пример похож на предыдущий, но кода в нем меньше:
>>> poem = ''
>>> fin = open('relativity', 'rt' )
>>> for line in fin:
... poem += line
...
>>> fin.close()
>>> len(poem)
150
Во всех предыдущих примерах в результате получалась одна строка poem. Функция readline() считывает по одной строке за раз и возвращает список этих строк:
>>> fin = open('relativity', 'rt' )
>>> lines = fin.readlines()
>>> fin.close()
>>> print(len(lines), 'lines read')
5 lines read
>>> for line in lines:
... print(line, end='')
...
There was a young lady named Bright,
Whose speed was far faster than light;
She started one day
In a relative way,
And returned on the previous night.>>>
Мы указали функции print() не добавлять автоматически символы новой строки, поскольку первые четыре строки сами их имеют. В последней строке этого символа не было, что заставило интерактивное приглашение появиться сразу после последней строки.
Если вы включите символ 'b' в строку режима, файл откроется в бинарном режиме. В этом случае вы будете читать и записывать байты, а не строки.
У нас под рукой нет бинарного стихотворения, поэтому мы просто сгенерируем 256 байтовых значений от 0 до 255:
>>> bdata = bytes(range(0, 256))
>>> len(bdata)
256
Откроем файл для записи в бинарном режиме и запишем все данные сразу:
>>> fout = open('bfile', 'wb')
>>> fout.write(bdata)
256
>>> fout.close()
И вновь функция write() возвращает количество записанных байтов.
Как и при работе с текстом, вы можете записывать бинарные данные фрагментами:
>>> fout = open('bfile', 'wb')
>>> size = len(bdata)
>>> offset = 0
>>> chunk = 100
>>> while True:
... if offset > size:
... break
... fout.write(bdata[offset:offset+chunk])
... offset += chunk
...
100
100
56
>>> fout.close()
Это достаточно просто. Все, что вам нужно, — открыть файл в режиме 'rb':
>>> fin = open('bfile', 'rb')
>>> bdata = fin.read()
>>> len(bdata)
256
>>> fin.close()
Если вы забудете закрыть файл, его закроет Python после того, как будет удалена последняя ссылка на этот файл. Таким образом, если вы откроете файл и не закроете его явно, он будет закрыт автоматически по завершении функции. Но вы можете открыть файл внутри длинной функции или даже основного раздела программы. Файл должен быть закрыт, чтобы все оставшиеся операции записи были завершены.
У Python имеются менеджеры контекста для очистки таких объектов, как открытые файлы. Вы можете использовать конструкцию withвыражениеasпеременная:
>>> with open('relativity', 'wt') as fout:
... fout.write(poem)
...
Вот и все. После того как блок кода, расположенный под менеджером контекста (в данном случае это одна строка), завершится (или нормально, или путем генерации исключения), файл закроется автоматически.
В процессе чтения и записи Python отслеживает ваше местоположение в файле. Функция tell() возвращает ваше текущее смещение от начала файла в байтах. Функция seek() позволяет перейти к другому смещению в файле. Это значит, что вам не обязательно прочитывать каждый байт файла, чтобы добраться до последнего, — вы можете использовать функцию seek(): сместиться именно к нужному байту и считать его.
Для примера воспользуемся 256-байтным бинарным файлом 'bfile', который мы создали ранее:
>>> fin = open('bfile', 'rb')
>>> fin.tell()
0
Используем функцию seek(), чтобы перейти к предпоследнему байту файла:
>>> fin.seek(255)
255
Считаем все данные от текущей позиции до конца файла:
>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255
Функция seek() также возвращает текущее смещение.
Вы также можете вызвать функцию seek(), передав ей второй аргумент: seek(offset,origin):
• если значение origin равно 0 (по умолчанию), вы сместитесь на offset байтов от начала файла;
• если значение origin равно 1, вы сместитесь на offset байтов с текущей позиции;
• если значение origin равно 2, вы сместитесь на offset байтов от конца файла.
Эти значения также определены в стандартном модуле os:
>>> import os
>>> os.SEEK_SET
0
>>> os.SEEK_CUR
1
>>> os.SEEK_END
2
Благодаря этому последний байт можно считать разными способами:
>>> fin = open('bfile', 'rb')
Один байт перед концом файла:
>>> fin.seek(-1, 2)
255
>>> fin.tell()
255
Считать данные до конца файла:
>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255
Вам не нужно вызывать функцию tell(), чтобы работала функция seek(). Я только хотел показать, что обе эти функции возвращают одинаковое смещение.
Рассмотрим случай, когда мы вызываем функцию seek(), чтобы сместиться с текущей позиции:
>>> fin = open('bfile', 'rb')
Следующий пример переносит позицию за 2 байта до конца файла:
>>> fin.seek(254, 0)
254
>>> fin.tell()
254
Теперь перейдем вперед на 1 байт:
>>> fin.seek(1, 1)
255
>>> fin.tell()
255
Наконец, считаем все данные до конца файла:
>>> bdata = fin.read()
>>> len(bdata)
1
>>> bdata[0]
255
Эти функции наиболее полезны при работе с бинарными файлами. Вы можете использовать их и для работы с текстовыми файлами, но если файл состоит не только из символов формата ASCII (каждый из которых занимает по 1 байту в памяти), вам будет трудно определить смещение. Ведь в таком случае оно будет зависеть от кодировки текста, а самая популярная кодировка (UTF-8) использует разное количество байтов для разных символов.
Альтернативой чтению и записи файла является его отображение в памяти (memory-map) с помощью стандартного модуля mmap. Такой подход позволяет представить содержимое файла как bytearray в памяти. Для получения более подробной информации обратитесь к документации () и примерам ().
В Python, как и во многих других языках программирования, операции для работы с файлами основаны на аналогичных операциях ОС Unix. Некоторые функции, такие как chown() и chmod(), называются одинаково, но теперь появились и новые названия. Сначала я покажу, как Python решает задачи с помощью функций из модуля os.path, а затем — с помощью более нового модуля pathlib.
Если хотите убедиться в том, что файл или каталог действительно существуют, а не являются плодом вашего воображения, воспользуйтесь функцией exists(). Передайте функции относительное или абсолютное имя файла, как показано здесь:
>>> import os
>>> os.path.exists('oops.txt')
True
>>> os.path.exists('./oops.txt')
True
>>> os.path.exists('waffles')
False
>>> os.path.exists('.')
True
>>> os.path.exists('..')
True
Функции, показанные в этом подразделе, проверяют, ссылается ли имя на файл, каталог или символьную ссылку (см. примеры, которые располагаются после описания ссылок).
Первой мы рассмотрим функцию isfile(). Она задает простой вопрос: перед нами находится старый добрый законопослушный файл?
>>> name = 'oops.txt'
>>> os.path.isfile(name)
True
Вот так можно определить папку:
>>> os.path.isdir(name)
False
Одна точка (.) является сокращением для текущей папки, а две точки (..) — для родительской. Эти папки существуют всегда, поэтому следующее выражение вернет результат True:
>>> os.path.isdir('.')
True
Модуль os содержит множество функций, работающих с pathname (именем пути) — полным именем файла, начинающимся с символа / и включающим в себя имена всех вложенных файлов. Одна из таких функций, isabs(), определяет, является ли аргумент абсолютным путем. Аргумент не обязательно должен быть именем реально существующего файла:
>>> os.path.isabs(name)
False
>>> os.path.isabs('/big/fake/name')
True
>>> os.path.isabs('big/fake/name/without/a/leading/slash')
False
Функция copy() находится в другом модуле, shutil. В этом примере файл oops.txt копируется в файл ohno.txt:
>>> import shutil
>>> shutil.copy('oops.txt', 'ohno.txt')
Функция shutil.move() копирует файл, а затем удаляет оригинал.
Эта функция соответствует своему названию. В этом примере файл ohno.txt переименовывается в ohwell.txt:
>>> import os
>>> os.rename('ohno.txt', 'ohwell.txt')
В операционных системах семейства Unix файл находится в одном определенном месте, но может иметь несколько имен, называемых ссылками. В низкоуровневых жестких ссылках найти все имена заданного файла не так уж легко. Символьная ссылка — это альтернативный метод, который сохраняет новое имя в своем собственном файле, позволяя вам получить одновременно оба имени — оригинальное и новое. Вызов link() создает жесткую ссылку, а symlink() — символьную ссылку. Функция islink() проверяет, является ли файл символьной ссылкой.
Вот так можно создать жесткую ссылку на существующий файл oops.txt из нового файла yikes.txt:
>>> os.link('oops.txt', 'yikes.txt')
>>> os.path.isfile('yikes.txt')
True
Для того чтобы создать символьную ссылку на существующий файл oops.txt из нового файла jeepers.txt, используйте следующий код:
>>> os.path.islink('yikes.txt')
False
>>> os.symlink('oops.txt', 'jeepers.txt')
>>> os.path.islink('jeepers.txt')
True
В системах Unix функция chmod() вносит изменения в права на доступ к файлу. Существуют права на чтение, запись и выполнение файла для пользователя (обычно того, кто создавал файл), а также для какой-то группы, в которой состоит пользователь, и для всех остальных. Команда принимает сильно сжатое восьмеричное значение (в системе счисления с основанием 8), в котором указаны пользователь, группа и другие, кто имеет доступ. Например, для указания того, что файл oops.txt для чтения доступен только своему владельцу, нужно ввести следующий код:
>>> os.chmod('oops.txt', 0o400)
Если вы не хотите работать с загадочными восьмеричными значениями и предпочитаете иметь дело с более понятными символами, можете импортировать некоторые константы из модуля stat и использовать такое выражение:
>>> import stat
>>> os.chmod('oops.txt', stat.S_IRUSR)
Эта функция также характерна для систем Unix/Linux/Mac. Вы можете изменить владельца и/или группу, указав числовой идентификатор пользователя ID (uid) и идентификатор группы (gid):
>>> uid = 5
>>> gid = 22
>>> os.chown('oops', uid, gid)
В этом фрагменте мы используем функцию remove() и попрощаемся с файлом oops.txt:
>>> os.remove('oops.txt')
>>> os.path.exists('oops.txt')
False
В большинстве операционных систем файлы существуют в рамках иерархии каталогов (иначе их еще называют папками). Контейнером для всех этих файлов и каталогов служит файловая система (иногда ее называют томом). Стандартный модуль os работает с такой иерархией и предоставляет функции, с помощью которых ею можно манипулировать.
В этом примере показывается, как создать каталог poems, в котором мы сохраним предыдущее стихотворение:
>>> os.mkdir('poems')
>>> os.path.exists('poems')
True
Немного подумав, вы решили, что этот каталог вам не нужен. Удалить его можно так:
>>> os.rmdir('poems')
>>> os.path.exists('poems')
False
О’кей, дубль два: снова создадим файл poems и что-нибудь в него запишем:
>>> os.mkdir('poems')
Теперь получим список всех файлов, содержащихся в этом каталоге (их пока нет):
>>> os.listdir('poems')
[]
Далее создадим подкаталог:
>>> os.mkdir('poems/mcintyre')
>>> os.listdir('poems')
['mcintyre']
Создайте в подкаталоге файл (не вводите все эти строки, если только не хотите почувствовать себя поэтом, а просто убедитесь, что начинаете и заканчиваете соответствующими кавычками, одинарными или тройными):
>>> fout = open('poems/mcintyre/the_good_man', 'wt')
>>> fout.write('''Cheerful and happy was his mood,
... He to the poor was kind and good,
... And he oft' times did find them food,
... Also supplies of coal and wood,
... He never spake a word was rude,
... And cheer'd those did o'er sorrows brood,
... He passed away not understood,
... Because no poet in his lays
... Had penned a sonnet in his praise,
... 'Tis sad, but such is world's ways.
... ''')
344
>>> fout.close()
Наконец, проверьте, что получилось. Хорошо, если бы файл там был:
>>> os.listdir('poems/mcintyre')
['the_good_man']
С помощью этой функции вы можете переходить из одной папки в другие. Покинем текущую папку и проведем немного времени в каталоге poems:
>>> import os
>>> os.chdir('poems')
>>> os.listdir('.')
['mcintyre']
Функция glob() ищет совпадающие имена файлов или каталогов, используя правила оболочки Unix, а не более полный синтаксис регулярных выражений. Эти правила выглядят так:
•* — совпадает со всем (в регулярных выражениях аналогом выступает .*);
• ? — совпадает с одним символом;
• [abc] — совпадает с символами a, b или c;
•[!abc] — совпадает со всеми символами, кроме a, b или c.
Получим все файлы и каталоги, имена которых начинаются с буквы m:
>>> import glob
>>> glob.glob('m*')
['mcintyre']
А как насчет файлов и каталогов с именами, состоящими из двух символов?
>>> glob.glob('??')
[]
Я думаю о слове из восьми букв, которое начинается на m и заканчивается на e:
>>> glob.glob('m??????e')
['mcintyre']
Как насчет чего-то, что начинается с букв k, l или m и заканчивается на букву e?
>>> glob.glob('[klm]*e')
['mcintyre']
Практически во всех компьютерах используется иерархическая файловая система, в которой каталоги (папки) содержат файлы и другие каталоги произвольного уровня вложенности. Если вы хотите обратиться к определенному файлу или каталогу, вам необходимо указать его путь (pathname): последовательность каталогов, необходимых для того, чтобы добраться до искомого объекта. Последовательность может быть абсолютной и начинаться сверху (от корня), а может быть относительной и начинаться с вашего текущего каталога.
Вы часто слышите, как люди путают прямой слеш (это символ '/', а не музыкант из группы Guns N’ Roses) и обратный слеш '\'. В ОС семейств Unix и Macs (а также в URL) как разделитель пути применяется слеш, а в ОС Windows — обратный слеш.
Python позволяет использовать слеш в качестве разделителя пути, когда вы указываете имена. В Windows вы можете задействовать обратный слеш, но при этом обратный слеш также является распространенным символом в escape-последовательностях в Python, поэтому вы должны его удваивать или использовать необработанные строки Python:
>>> win_file = 'eek\\urk\\snort.txt'
>>> win_file2 = r'eek\urk\snort.txt'
>>> win_file
'eek\\urk\\snort.txt'
>>> win_file2
'eek\\urk\\snort.txt'
При создании pathname вы можете сделать следующее:
• использовать подходящий символ разделителя пути ('/' или '\');
• построить путь (см. подраздел «Построение пути с помощью os.path.join()» далее в этой главе);
• использовать модуль pathlib (см. подраздел «Модуль pathlib» также в этой главе).
Эта функция расширяет относительное имя до абсолютного. Если ваш текущий каталог — /usr/gaberlunzie и файл oops.txt находится там же, можете воспользоваться следующим кодом:
>>> os.path.abspath('oops.txt')
'/usr/gaberlunzie/oops.txt'
В одном из предыдущих разделов мы создавали символьную ссылку на файл oops.txt из нового файла jeepers.txt. При похожих обстоятельствах вы можете получить имя файла oops.txt из файла jeepers.txt с помощью функции realpath(), как показано здесь:
>>> os.path.realpath('jeepers.txt')
'/usr/gaberlunzie/oops.txt'
Когда вы создаете составное имя пути, вы можете вызвать функцию os.path.join() и объединить части попарно, используя правильный для вашей ОС символ-разделитель:
>>> import os
>>> win_file = os.path.join("eek", "urk")
>>> win_file = os.path.join(win_file, "snort.txt")
Если я запущу эту функцию на ПК с ОС Mac или Linux, я получу следующее:
>>> win_file
'eek/urk/snort.txt'
Запуск в ОС Windows даст такой результат:
>>> win_file
'eek\\urk\\snort.txt'
Но если один и тот же код выдает разные результаты в зависимости от того, где он был запущен, это может привести к проблемам. Новый модуль pathlib является портативным решением этой проблемы.
В Python модуль pathlib добавился в версии 3.4. Он представляет собой альтернативу модулям os.path, которые мы только что рассмотрели. Зачем же нужен еще один модуль?
Вместо того чтобы считать пути строками, этот модуль создает объект типа Path, чтобы работать с ними на более высоком уровне. Создайте объект типа Path с помощью вызова Path(), а затем склейте все элементы вашего пути с помощью простых слешей (а не символов '/'):
>>> from pathlib import Path
>>> file_path = Path('eek') / 'urk' / 'snort.txt'
>>> file_path
PosixPath('eek/urk/snort.txt')
>>> print(file_path)
eek/urk/snort.txt
Этот прием со слешами использует «магические методы» Python. Объект класса Path может немного рассказать о себе:
>>> file_path.name
'snort.txt'
>>> file_path.suffix
'.txt'
>>> file_path.stem
'snort'
Вы можете передать переменную file_path в функцию open(), как и любое имя файла или строку пути.
Вы также можете увидеть, что случится, если запустить эту программу в другой операционной системе или если вам нужно сгенерировать внешние пути на своем компьютере:
>>> from pathlib import PureWindowsPath
>>> PureWindowsPath(file_path)
PureWindowsPath('eek/urk/snort.txt')
>>> print(PureWindowsPath(file_path))
eek\urk\snort.txt
Для получения более подробной информации обратитесь к документации ().
Вы уже знаете, как изменять данные в памяти и как получать данные из файлов, а также записывать их внутрь. Но что делать в ситуации, когда у вас есть данные в памяти, но вам нужно вызвать функцию, которая ожидает в качестве параметра файл (или наоборот)? Вам понадобится изменить данные и передать эти байты или символы, не создавая временных файлов.
Вы можете применить io.BytesIO для бинарных данных (байтов) и io.StringIO для текстовых данных (строк). И в том и в другом случае из данных будет создан файлоподобный объект, который подойдет для всех функций работы с файлами, рассмотренных в этой главе.
Одним из вариантов использования этих типов является преобразование формата данных. Поработаем, например, с библиотекой PIL (более подробно о ней вы сможете прочитать в подразделе «PIL и Pillow» на с. 475), которая считывает и записывает данные из изображения.
В качестве первого аргумента методов open() и save() класса Image ожидается имя файла или файлоподобный объект. Код в примере 14.1 использует тип BytesIO для того, чтобы считать и записать данные, лежащие в памяти. Он считывает один или несколько файлов изображений из командной строки, преобразовывает данные из них в три разных формата и выводит длины и первые 10 байт полученного результата.
Пример 14.1. convert_image.py
from io import BytesIO
from PIL import Image
import sys
def data_to_img(data):
"""Return PIL Image object, with data from in-memory <data>"""
fp = BytesIO(data)
return Image.open(fp) # выполняет чтение из памяти
def img_to_data(img, fmt=None):
"""Return image data from PIL Image <img>, in <fmt> format"""
fp = BytesIO()
if not fmt:
fmt = img.format # сохраняет оригинальный формат
img.save(fp, fmt) # записывает в память
return fp.getvalue()
def convert_image(data, fmt=None):
"""Convert image <data> to PIL <fmt> image data"""
img = data_to_img(data)
return img_to_data(img, fmt)
def get_file_data(name):
"""Return PIL Image object for image file <name>"""
img = Image.open(name)
print("img", img, img.format)
return img_to_data(img)
if __name__ == "__main__":
for name in sys.argv[1:]:
data = get_file_data(name)
print("in", len(data), data[:10])
for fmt in ("gif", "png", "jpeg"):
out_data = convert_image(data, fmt)
print("out", len(out_data), out_data[:10])
Поскольку объект BytesIO ведет себя как файл, вы можете вызывать для него функции seek(), read() и write() так же, как и для обычного файла. Если вы вызовете функцию seek(), а затем функцию read(), то получите только байты, которые находятся между текущей позицией файла и его концом. Функция getvalue() вернет все байты объекта BytesIO.
Перед вами полученные значения для изображения, которое вы увидите в главе 20:
$ python convert_image.py ch20_critter.png
img <PIL.PngImagePlugin.PngImageFile image mode=RGB size=154x141
at 0x10340CF28> PNG
in 24941 b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00'
out 14751 b'GIF87a\\x9a\\x00\\x8d\\x00'
out 24941 b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00'
out 5914 b'\\xff\xd8\\xff\\xe0\\x00\\x10JFIF'
Следующая глава будет чуточку сложнее. В ней мы рассмотрим конкурентность (способы выполнения нескольких операций одновременно) и процессы (запущенные программы).
14.1. Выведите на экран список всех файлов из текущего каталога.
14.2. Выведите на экран список всех файлов из родительского каталога.
14.3. Присвойте строку Thisisatestoftheemergencytextsystem переменной test1 и запишите эту переменную в файл с именем test.txt.
14.4. Откройте файл test.txt и считайте его содержимое в строку test2. Будут ли одинаковыми строки test1 и test2?
В первой рукописи этой книги я использовал термин «общая теория относительности» и был любезно исправлен редактором-физиком.
И почему же мы не подумали раньше?
Один из способов запомнить названия слешей: обычный наклонен вперед, а обратный — назад.
QDOS — это операционная система, которую приобрел Билл Гейтс за $50 000, чтобы создать MS-DOS, когда IBM начала интересоваться первым ПК. Она была похожа на CP/M, где слеши использовались в аргументах командной строки. Когда в MS-DOS появились каталоги, для них пришлось использовать обратные слеши.