Файлы и каталоги в Python
- Понятия файла и каталога
- Функция open: запись и чтение файлов
- Модуль pickle: сериализация объектов
- Модуль shelve: key/value хранилище объектов
- Модуль pathlib: работа с путями
- Модуль shutil: операции над файлами и каталогами
- Краткие итоги параграфа
- Вопросы и задания для самоконтроля
Понятия файла и каталога
Любая программа в конечном итоге представляет собой обычный набор файлов, сгруппированных по какому-либо признаку по каталогам программы. Поэтому в Python предусмотрен внушительный набор инструментов для работы с файлами и каталогами, которые позволяют выполнять над ними такие операции как создание, удаление, переименование, перемещение, чтение, запись, перезапись и многие другие полезные операции. Но что представляют из себя файлы и каталоги?
Файл (от англ. file) – это именованная область данных на носителе информации.
Имена файлов нужны для того, чтобы точно знать, к какой области данных носителя информации осуществляется запрос. При этом большинство операционных систем в целях однозначности не допускает использование двух файлов с полностью идентичными именами в одном каталоге. Что касается набора символов, которые разрешается использовать для имен файлов, а также максимально допустимой длины имен файлов, то они зависят от используемой файловой системы. Например, Windows допускает использование в имени файла заглавных и строчных букв, цифр, некоторых знаков препинания и пробела, но запрещает использование символов >, <, /, \, |, ?, *, :, ". При этом максимально допустимая длина имен файлов в различных системах обычно не превышает 256 символов.
Чтобы помочь системе определить тип файла и, соответственно, приложение для работы с ним, в имени файла обычно указывается его расширение, которое отделяется от остальной части имени точкой. Примерами расширений файлов могут служить: .txt – обычный текстовый файл, .py – файл с кодом Пайтона, .html – файл, представляющий собой html-документ, .jpg – файл изображения в формате jpg и огромное число других расширений.
Когда файлов становится слишком много, возникает необходимость их упорядочивания и группировки. Для этих целей используются каталоги или директории.
Каталог или директория (от англ. directory) – это специальный объект файловой системы, который используется для упрощения организации файлов. В графическом пользовательском интерфейсе каталоги также называют папками. В любом случае каталог представляет собой файл, содержащий записи о входящих в него файлах или других каталогах.
Каталог, прямо или косвенно включающий в себя все прочие каталоги и файлы программы, называется корневым. А каталог, в котором находится текущий каталог, называется родительским каталогом.
Как было сказано выше, большинство операционных систем не разрешает использование двух файлов с полностью идентичными именами в одном каталоге. В то же время вполне допустимо создавать для одного и того же файла несколько имен при помощи жестких ссылок.
Жёсткими ссылками или хардлинками (от англ. hard link) называют различные имена одного и того же файла, которые могут создаваться в пределах одного физического носителя.
После создания жесткой ссылки сказать где настоящий файл, а где хардлинк невозможно, так как они полностью равноправны. Сама же область данных существует до тех пор, пока не будут удалены полностью все имена. Кроме того, при редактировании файла через одну из ссылок на него, содержимое по другим ссылкам также изменяется.
Как следует из определения, создание жестких ссылок одного и того же файла на разных физических носителях (например, жестких дисках) невозможно. Однако данное ограничение можно обойти при помощи символических ссылок.
Символическая ссылка или симлинк (от англ. symbolic link) – это специальный файл в файловой системе, содержащий в себе ссылку на другой файл или директорию, в том числе, и расположенный на другом физическом носителе.
Символическая ссылка занимает ровно столько места в файловой системе, сколько требуется для записи её содержимого, которое представляет собой строку, содержащую путь к определенному файлу или каталогу. Если символьная ссылка указывает на файл, который не существует, ее называют битой. При попытке обращения к файлу посредством битой ссылки обычно выводится соответствующее предупреждение.
Рассмотрим основные встроенные возможности языка Python для работы с файлами и каталогами.
Функция open в Python: запись и чтение файлов
Редактирование содержимого файла подразумевает осуществление таких операций, как открытие и закрытие файла, чтение и запись, получение текущего значения файлового указателя и его перемещение в соответствии с заданными параметрами, сброс файлового буфера и некоторые другие операции. При этом, используя для редактирования соответствующие функции Python, нужно иметь также и некоторое представление о понятиях, которые имеют непосредственное отношение к работе с файлами. Приведем краткое описание наиболее важных из них.
- Файловый дескриптор (от англ. file descriptor) – неотрицательное целое число, которое операционная система возвращает при открытии файла (в случае, если это возможно) и с помощью которого выполняются все остальные файловые операции. По завершении операций и закрытии файла дескриптор теряет смысл.
- Файловый указатель – число, указывающее величину смещения относительно нулевого байта в файле. Обычно по этому адресу осуществляется чтение/запись, если конечно вызов операции чтения/записи не предусматривает указание адреса. При выполнении операций чтения/записи файловый указатель смещается на число прочитанных/записанных байт. В результате последовательный вызов операций чтения позволяет прочитать весь файл не обращая внимания на его размер.
- Файловый буфер – специально выделенный участок памяти (буфер), в котором операционная система осуществляет кэширование файловых операций. При закрытии файла буфер сбрасывается.
- Режим доступа – режим устанавливающий разрешения на чтение и запись файла при его открытии.
Для чтения и записи файлов в Python предназначена встроенная функция open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None), которая возвращает специальный итерируемый файловый объект, связанный с целевым файлом. Если файл не может быть открыт, бросается исключение OSError. Обязательным является только первый параметр, в качестве которого необходимо передавать строку с путем к файлу. Все остальные параметры необязательны. Пройдемся по ним.
- file – строка, содержащая абсолютное или относительное значение пути к файлу.
-
mode='r' – строка с индикатором режима, в котором следует открыть файл:
- 'r' – открыть файл на чтение, поместив файловый указатель в начало файла, либо сгенерировать исключение, если файл с таким именем найден не будет (значение используется по умолчанию).
- 'w' – открыть файл на запись (содержимое файла удаляется, а если его нет, то создается новый).
- 'x' – создать файл и открыть его на запись либо сгенерировать исключение FileExistsError, если файл с таким именем уже существует.
- 'a' – открыть файл на дозапись либо создать новый, если файл с таким именем найден не будет (содержимое существующего файла не удаляется, а запись новой информации осуществляется в конец файла).
- 'b' – открыть файл в бинарном режиме (используется как дополнительный флаг, например, 'rb', 'wb' или 'ab').
- 't' – открыть файл в текстовом режиме (используется как дополнительный флаг по умолчанию).
-
'+' – открыть файл на чтение и запись одновременно (используется как дополнительный флаг):
- 'r+' – открыть существующий файл на чтение и запись, поместив файловый указатель в начало файла (если файл не существует будет брошено исключение);
- 'w+' – открыть файл на запись и чтение, удалив при этом его содержимое (если файл не существует, он будет сперва создан);
- 'x+' – создать файл и открыть его на запись и чтение либо сгенерировать исключение FileExistsError, если файл с таким именем уже существует;
- 'a+' – открыть существующий файл на дозапись и чтение, не удаляя содержимое и поместив файловый указатель в конец файла, либо создать новый файл в случае его отсутствия (при чтении следует не забывать перемещать файловый указатель в нужную позицию файла);
- 'rb+' или 'r+b' – открыть существующий файл на чтение и запись в бинарном режиме, поместив файловый указатель в начало файла (если файл не существует будет брошено исключение);
- 'wb+' или 'w+b' – открыть существующий файл на запись и чтение в бинарном режиме, удалив при этом его содержимое (если файл не существует, он будет сперва создан);
- 'xb+' или 'x+b'– создать файл и открыть его на запись и чтение в бинарном режиме либо сгенерировать исключение FileExistsError, если файл с таким именем уже существует;
- 'ab+' или 'a+b' – открыть существующий файл на дозапись и чтение в бинарном режиме, не удаляя содержимое и поместив файловый указатель в конец файла, либо создать новый файл в случае его отсутствия (при чтении следует не забывать перемещать файловый указатель в нужную позицию файла).
-
buffering=-1 – используемый режим буферизации, задаваемый целым числом:
- 0 – отключить буферизацию (только для бинарного режима).
- 1 – построчная буферизация (только для текстового режима).
- n > 1 – размер буфера в байтах.
- -1 – значение по умолчанию: для текстовых файлов, используется построчная буферизация, а двоичные файлы буферизируются кусками фиксированного размера. Для многих систем буфер равен 4096 или 8192 байт. Если же размер буфера определить не удается, используется io.DEFAULT_BUFFER_SIZE.
- encoding=None – строка с указанием кодировки, которая будет использоваться для декодирования или кодирования текстового файла (по умолчанию для файлов в Python используется кодировка 'utf-8').
- errors=None – строка, определяющая обработчик ошибок кодирования и декодирования (не используется в бинарном режиме). Подробнее смотрите в документации (если вдруг понадобится).
- newline=None – выбор режима перевода строк в текстовых файлах: None, '\n', '\r' или '\r\n'.
- closefd=True – флаг закрытия файлового дескриптора: True или False.
- opener=None – пользовательский объект, поддерживающий вызов, который следует использовать для открытия файла. Этот объект получая на входе file и closefd, должен возвращать открытый дескриптор файла.
Стоит заметить, что использовать все параметры функции open() вовсе необязательно, т.к. в большинстве случаев для открытия и работы с файлами достаточно указать путь к файлу, режим работы и кодировку (см. пример №1).
# Создаем в текущей папке скрипта файл test.txt,
# открывая его для записи и сохраняя возвращаемый
# функцией файловый объект в переменной f.
f = open('test.txt', 'w', encoding='utf-8')
# Записываем в него строку.
f.write('Строка №1.')
# Не забываем закрыть файловый объект.
f.close()
# Открываем test.txt для чтения.
f = open('test.txt', encoding='utf-8')
# Читаем его целиком.
s = f.read()
# Закрываем файловый объект.
f.close()
# Выводим содержимое на экран.
print(s)
# Открываем test.txt для дозаписи.
f = open('test.txt', 'a', encoding='utf-8')
# Дописываем в него еще 2 строки.
li = ['\nСтрока №2.\n', 'Строка №3.']
f.writelines(li)
# Закрываем файловый объект.
f.close()
Строка №1.
Строка №2.
Строка №3.
Пример №1. Чтение и запись файлов в Python (часть 1).
Как видим, после получения файлового объекта, возвращаемого функцией open(), редактирование данных в открытом файле производится с помощью специально предназначенного для этого набора методов. Перечислим некоторые из них.
- read([size]) – считывает из файла не более size байтов. Если же конец файла EOF достигается до получения указанного размера, метод считывает только доступные байты. В текстовом режиме возвращается строка, в бинарном – байтовый объект.
- readline([size]) – читает и затем возвращает одну целую строку из файла, включая конечный символ \n (если файл открыт в бинарном режиме, метод возвращает байтовый объект). Если указать неотрицательный аргумент size, строка будет считываться частями по size байтов до тех пор, пока не будет достигнут символ новой строки \n. При отрицательном значении size строка будет считываться полностью. В любом случае при достижении конца файла EOF метод вернет пустую строку.
- readlines(sizehint) – возвращает список строк или байтовых объектов файла в зависимости от режима доступа к файлу, включая конечные символы \n строк. Опять же, при достижении конца файла EOF возвращается пустая строка. При наличии необязательного аргумента sizehint читаются целые строки, составляющие приблизительно sizehint байт (округление производится до внутреннего размера буфера).
- write(str) – записывает в файл строку str с учетом текущей позиции файлового указателя чтения/записи и возвращает количество записанных байт в виде целого числа. Имейте в виду, что если файловый указатель находится не в конце файла, старые данные в нем по пути записи будут потеряны. Кроме того, из-за буферизации строка может не отображаться в файле до тех пор, пока не будет вызван метод flush() или close(). Поскольку различные операционные системы имеют собственные соглашения по обозначению конца строки, следует быть внимательным при записи данных в текстовый файл, правильно устанавливая символы конца строки в соответствии с операционной системой, под которой работает скрипт: \n для Unix/Linux, \r\n для Windows и \r для Macintosh.
- writelines(seq) – записывает в файл последовательность строк seq, которой может быть любой итерируемый объект, содержащий строки в качестве элементов. При этом разделители строк по мере необходимости должны добавляться вручную. Опять же, следует следить за текущей позицией файлового указателя во избежание потери данных по пути записи.
- truncate([size]) – усекает размер файла в байтах по текущему указателю чтения/записи и возвращает size. Если необязательный аргумент size указан, файл усекается до этого размера. Метод не будет работать, если файл открыт в режиме только чтения.
-
seek(offset, [whence]) – устанавливает файловый указатель чтения/записи в требуемую позицию offset, которая
по умолчанию отсчитывается в байтах от начала файла. Необязательный параметр whence может иметь три значения:
- 0 – указатель будет смещен на offset относительно начала файла (используется по умолчанию);
- 1 – указатель будет смещен на offset относительно текущей позиции указателя;
- 2 – указатель будет смещен на offset относительно конца файла.
- tell() – возвращает текущую позицию файлового указателя чтения/записи в байтах.
- flush() – очищает внутренний буфер и перемещает буферизованные данные на диск. Обычно используется только для выходного потока. Метод возвращает количество буферизованных символов или байт.
- close() – закрывает открытый файл, после чего он больше не может быть прочитан или записан. Для этого придется открыть файл заново, иначе будет вызвано исключение ValueError.
Следует всегда закрывать открытый файл после окончания работы с ним, если только файл не был открыт с помощью менеджера контекста with/as, т.к. в этом случае закрытие файла осуществляется в автоматическом режиме (см. пример №2).
# Откроем наш файл test.txt на чтение и дозапись.
with open('test.txt', 'a+', encoding='utf-8') as f:
# Дописываем в него еще 2 строки.
li = ['\nСтрока №4.\n', 'Строка №5.']
f.writelines(li)
# Перемещаем файловый указатель в начало файла.
f.seek(0)
# Т.к. файловый объект итерируем и его эл-ми
# являются строки, можно сделать так.
for s in f: print(s, end='')
print('\n')
# Откроем наш файл test.txt на чтение.
with open('test.txt', encoding='utf-8') as f:
# Т.к. по достижении конца файла EOF
# возвращается пустая строка, можно и так.
while s: print(s := f.readline(), end='')
Строка №1.
Строка №2.
Строка №3.
Строка №4.
Строка №5.
Строка №1.
Строка №2.
Строка №3.
Строка №4.
Строка №5.
Пример №2. Чтение и запись файлов в Python (часть 2).
Остальные методы для работы с потоками можно посмотреть в подразделе «io — Core tools for working with streams» стандартной библиотеки.
Стоит добавить, что после открытия текстового файла в соответствующем режиме, запись в файл можно осуществить и при помощи обычной функции print(), передав ей полученный файловый объект в виде значения именованного аргумента file (см. пример №3).
# Открываем файл на запись.
f = open('test.txt', 'w')
# Записываем строку.
print('Ok', file=f)
# Закрываем файл.
f.close()
Пример №3. Чтение и запись файлов в Python (часть 3).
Модуль pickle в Python: сериализация объектов
Для сериализации и десериализации объектов в Python предназначен модуль pickle, расположенный в подразделе «pickle — Python object serialization» стандартной библиотеки. Он позволяет преобразовывать объекты как в поток байтов (сериализация), так и обратно из байтового состояния в объекты (десериализация). А поскольку поток байтов можно легко записывать в файл, данный модуль широко используется для долговременного хранения и последующей загрузки различных сложных python-объектов. Перечислим основные методы, предоставляемые данным модулем.
- pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None) – сериализует и записывает объект obj в файловый объект file, возвращая затем строку байтов (т.е. pickle.dumps()). Необязательные аргументы fix_imports, encoding и errors используются для управления поддержкой совместимости потока pickle, генерируемого Python 2. Нам они не понадобятся, т.к. мы работаем в Python 3.
- pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict") – десериализует объект из файлового объекта file и затем возвращает его.
- pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None) – сериализует объект (т.е. преобразует в двоичное представление) и затем возвращает его.
- pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict") – десериализует объект из двоичного представления и затем возвращает его.
Посмотрим, как эти методы работают на практике (см. пример №4).
# Импортируем модуль.
import pickle
# Пусть у нас есть словарь.
db_devices = {'Мышь': 3, 'Клава': 7}
# Открываем db_file.pkl на запись в двоичном режиме.
f = open('db_file.pkl', 'wb')
# Сериализуем и записываем в него наш словарь.
pickle.dump(db_devices, f)
# Не забываем закрыть файловый объект.
f.close()
# Откроем файл db_file.pkl на чтение.
f = open('db_file.pkl', 'rb')
# Выгружаем оттуда наш объект (десериализуем).
db_devices = pickle.load(f)
# Не забываем закрыть файловый объект.
f.close()
# Посмотрим, что мы загрузили.
print(db_devices)
# Изменим словарь.
db_devices['Мышь'] = 11
# Откроем файл db_file.pkl на перезапись.
f = open('db_file.pkl', 'wb')
# Сериализуем и записываем в него наш словарь.
pickle.dump(db_devices, f)
# Не забываем закрыть файловый объект.
f.close()
# Откроем файл db_file.pkl на чтение.
f = open('db_file.pkl', 'rb')
# Посмотрим, что у нас хранится там теперь.
print(pickle.load(f))
# Не забываем закрыть файловый объект.
f.close()
{'Мышь': 3, 'Клава': 7}
{'Мышь': 11, 'Клава': 7}
Пример №4. Использование модуля pickle (часть 1).
Как видим, благодаря модулю pickle мы с легкостью записали наш словарь в файл, затем извлекли его для изменения и записали обратно. Т.е. по сути мы создали простейшую базу данных для длительного хранения наших объектов. Конечно, работать с объемной базой данных таким образом вряд ли получится, ведь даже для малейших изменений придется читать и перезаписывать весь файл целиком. Однако никто не запрещает, например, создать для каждой записи свой файл и при необходимости извлекать данные по отдельности (см. пример №5).
# Импортируем модуль.
import pickle, os
# Пусть, например, у нас есть словарь.
db_devices = {
'mouse': {'кол-во': 9, 'цена': 1.1},
'keyboard': {'кол-во': 3, 'цена': 2.3},
'monitor': {'кол-во': 2, 'цена': 90.5}
}
# Имя каталога с файлами нашей базы данных.
db_dir = 'db_devices'
# Если каталог базы данных еще не создан.
if not os.path.exists(db_dir):
# Создаем пакет в каталоге скрипта.
os.mkdir(db_dir)
# Если нужно, создаем файл инициализации.
# with open(db_dir + '/__init__.py', 'w') as f: pass
# Организуем цикл по элементам словаря.
for elem in db_devices:
# Записи пишем в разные файлы нашего каталога.
elem_path = db_dir + '/' + elem + '.pkl'
# Использ. менеджер контекста для удобства.
with open(elem_path, 'wb') as f:
# Сериализуем очередное значение словаря.
pickle.dump(db_devices[elem], f)
# Извлечем какие-нибудь сохраненные данные.
with open(db_dir + '/mouse.pkl', 'rb') as f:
# Десериализуем объект и сохраняем в переменной.
db_mouse = pickle.load(f)
# Посмотрим, что мы загрузили.
print(db_mouse)
# Изменим значение словаря (запись базы данных).
db_mouse['кол-во'] = 7
# Сохраним изменения в нашей базе данных.
with open('db_devices/mouse.pkl', 'wb') as f:
# Сериализуем объект и перезаписываем файл.
db_mouse = pickle.dump(db_mouse, f)
# Проверим изменение данных.
with open(db_dir + '/mouse.pkl', 'rb') as f:
# Десериализуем объект и сохраняем в переменной.
print(pickle.load(f))
{'кол-во': 9, 'цена': 1.1}
{'кол-во': 7, 'цена': 1.1}
Пример №5. Использование модуля pickle (часть 2).
При желании можно было бы оформить обращение к записям нашей базы данных в виде функций или вообще создать отдельный класс для работы с ней. Главное здесь помнить о том, что не стоит загружать с помощью модуля pickle файлы из ненадёжных источников, т.к. это может привести к непредвиденным и печальным последствиям.
Модуль shelve в Python: key/value хранилище объектов
Если к сохраненным в файлах записям необходим доступ по ключу, вместо модуля pickle удобнее использовать более высокоуровневый инструмент стандартной библиотеки в виде модуля shelve (см. подраздел «shelve — Python object persistence»). Данный модуль реализует постоянное хранилище для произвольных python-объектов, значения которого можно извлекать, используя синтаксис и методы обычного словаря со строковыми ключами.
Осуществляется создание либо открытие такого хранилища с помощью метода shelve.open(filename, flag='c', protocol=None, writeback=False), где
- filename – путь к создаваемому хранилищу и его имя, но без какого-либо расширения. При этом целевой каталог хранилища должен уже существовать, иначе будет получено исключение.
-
flag – режим открытия хранилища:
- 'c' – хранилище открывается для чтения и записи (используется по умолчанию). Если такого хранилища не существует, то оно вначале создается.
- 'r' – хранилище открывается для чтения.
- 'w' – хранилище открывается для записи.
- 'n' – хранилище открывается для перезаписи. Если такого хранилища не существует, то оно вначале создается.
- protocol – версия используемого протокола pickle. В принципе, его можно не указывать. В этом случае будет использоваться версия по умолчанию для данной версии интерпретатора Python. Например, начиная с Python 3.8 по умолчанию используется четвертая версия протокола pickle, хотя уже появилась и более современная пятая его версия.
- writeback – кэширование записей в памяти. По умолчанию кэширование не производится, т.к. при одновременном доступе к объемным записям или значительному их количеству процесс закрытия хранилища может занимать длительное время из-за необходимости записи в него кешированных данных.
Результатом вызова shelve.open() является специальный объект для работы с открытым хранилищем, поддерживающий синтаксис и методы обычных словарей и обладающий двумя собственными методами:
- sync() – очищает кеш, записывая все записи из него на диск и синхронизируя хранилище, если оно было открыто с флагом writeback=True;
- close() – синхронизирует и закрывает хранилище или возбуждает ValueError, если оно уже было закрыто.
Рассмотрим использование возможностей модуля shelve для создания постоянного хранилища данных, с последующим добавлением в него новых данных или редактирования/удаления уже существующих (см. пример №6).
# Импортируем модуль shelve.
import shelve
# Импортируем класс для работы с путями.
from pathlib import Path
# Имя каталога с файлами нашего хранилища.
db_dir = 'db_devices'
# Если каталог хранилища еще не создан.
if not Path(db_dir).exists():
# Создаем пакет в каталоге скрипта.
Path(db_dir).mkdir()
# Если нужно, создаем файл инициализации.
# with open(db_dir + '/__init__.py', 'w') as f: pass
# Открываем хранилище (либо создаем, если его еще нет).
db_devices = shelve.open(db_dir + '/db_devices')
# Добавляем в хранилище данные, используя синтаксис
# обычного словаря со строковыми ключами.
db_devices['mouse'] = {'кол-во': 9, 'цена': 1.1}
db_devices['keyboard'] = {'кол-во': 3, 'цена': 2.3}
# Закрываем хранилище.
db_devices.close()
# Открываем хранилище повторно для чтения и записи
# без кэширования записей в памяти (writeback=False).
db_devices = shelve.open(db_dir + '/db_devices')
# Извлекаем нужные данные и выводим их на экран.
print('Мыши: ', db_devices['mouse']['кол-во'], end='\n\n')
# Поскольку кеширование записей отключено, нужно
# работать с временной копией хранилища.
temp_mouse = db_devices['mouse']
# Вносим нужные изменения в копию записи.
temp_mouse['кол-во'] = 5
# Вносим изменения в само хранилище.
db_devices['mouse'] = temp_mouse
# Добавляем еще одну запись в хранилище.
db_devices['monitor'] = {'кол-во': 2, 'цена': 90.5}
# Удаляем существующую запись из хранилища.
del db_devices['keyboard']
# Не забываем закрыть хранилище.
db_devices.close()
# Открываем хранилище еще раз.
db_devices = shelve.open(db_dir + '/db_devices')
# Выводим все данные хранилища на экран.
for key in db_devices:
print(key, '=>', db_devices[key])
# Закрываем хранилище.
db_devices.close()
Мыши: 9
mouse => {'кол-во': 5, 'цена': 1.1}
monitor => {'кол-во': 2, 'цена': 90.5}
Пример №6. Использование модуля shelve (часть 1).
Как видим, в простейшем случае для создания хранилища (или открытия уже существующего) нужно передать методу shelve.open() путь к создаваемому хранилищу и его имя, но без какого-либо расширения. В результате в целевом каталоге (либо в каталоге скрипта) интерпретатором будет создано три служебных файла с указанным именем и расширениями .bak, .dat и .dir. При этом целевой каталог хранилища должен существовать еще до начала операции, иначе будет получено исключение. Именно поэтому в нашем примере мы предварительно создали в директории скрипта каталог для хранилища db_devices и только затем указали его в качестве целевого каталога в вызове метода shelve.open(). В результате интерпретатор создал в нем три служебных файла хранилища: db_devices.bak, db_devices.dat и db_devices.dir.
После создания и открытия хранилища можно начинать записывать в него данные, используя для этого синтаксис обычного словаря со строковыми ключами. Здесь главное помнить, что по умолчанию writeback=False, т.е. кеширование данных в памяти, не используется. Поэтому для последующего редактирования изменяемых объектов хранилища их сперва нужно извлечь по ключу, сохранить копию во временной переменной, выполнить над переменной все необходимые операции и в конце записать измененный объект обратно в хранилище (удалив по необходимости и временную копию). В нашем случае последовательность инструкций была следующая: temp_mouse = db_devices['mouse'] (мы извлекли из хранилища целевой словарь и присвоили его переменной), temp_mouse['кол-во'] = 5 (внесли необходимые изменения в словарь), db_devices['mouse'] = temp_mouse (сохранили изменения в хранилище).
Помимо изменения записей открытого хранилища для него доступны также операции по добавлению новых записей и удалению уже существующих. Главное не забывать после завершения всех работ закрывать открытое хранилище с помощью метода close(). Опять же, при использовании менеджера контекста with/as использовать данный метод нет нужды, т.к. при выходе за область его видимости синхронизация с хранилищем и его закрытие будут произведены автоматически.
Использование параметра обратной записи writeback со значением True может быть весьма удобным, если заранее известно, что количество одновременных запросов к различным записям хранилища будет невелико, как и объем хранимых в них данных. В этом случае все результаты операций будут кешироваться в памяти, что несколько замедлит процесс закрытия хранилища из-за одновременного перемещения данных кеша на диск, но зато хранимые объекты станут доступны для непосредственной обработки с помощью имеющихся у них методов без необходимости создания каких-либо промежуточных копий объектов (см. пример №7).
# Импортируем модуль.
import shelve, os
# Путь сохраним в переменной.
p = 'db_devices/db_devices'
# Используем writeback=True.
with shelve.open(p, writeback=True) as db_devices:
# Проверим хранилище из предыдущего примера.
# mouse => {'кол-во': 5, 'цена': 1.1}
# monitor => {'кол-во': 2, 'цена': 90.5}
for key in db_devices:
print(key, '=>', db_devices[key])
# Изменим значение напрямую без копий.
db_devices['mouse']['кол-во'] = 27
# Изменения сохранились.
with shelve.open(p) as db_devices:
# mouse => {'кол-во': 27, 'цена': 1.1}
# monitor => {'кол-во': 2, 'цена': 90.5}
for key in db_devices:
print(key, '=>', db_devices[key])
mouse => {'кол-во': 5, 'цена': 1.1}
monitor => {'кол-во': 2, 'цена': 90.5}
mouse => {'кол-во': 27, 'цена': 1.1}
monitor => {'кол-во': 2, 'цена': 90.5}
Пример №7. Использование модуля shelve (часть 2).
В общем случае модуль shelve позволяет хранить в файлах не только словари, но и другие сложные объекты, сериализация которых поддерживается модулем pickle (см. пример №8).
# Импортируем модуль.
import shelve, os
# Если каталог хранилища еще не создан.
if not os.path.exists('db_objs'):
# Создаем пакет в каталоге скрипта.
os.mkdir('db_objs')
# Формируем путь и сохраняем его в переменной.
db_dir = 'db_objs/db_objs'
# Создаем объект функции.
def my_func(): print('my_func')
# Создаем класс.
class MyClass:
def get_name(self): return 'MyClass'
# Создаем список.
li = [[1, 2], {1: 'one', 2: 'two'}, 253]
# Создаем и открываем хранилище для записи.
with shelve.open(db_dir) as db_objs:
# Записываем всю компанию в хранилище.
db_objs['func'] = my_func
db_objs['cls'] = MyClass
db_objs['li'] = li
# Прочитаем все записи.
with shelve.open(db_dir) as db_objs:
# func => <function my_func at 0x000001F6F37D8D60>
# cls => <class '__main__.MyClass'>
# li => [[1, 2], {1: 'one', 2: 'two'}, 253]
for key in db_objs:
print(key, '=>', db_objs[key])
# Поэкспериментируем с записями.
with shelve.open(db_dir, writeback=True) as db_objs:
# Получаем доступ к объекту функции и вызываем ее.
# Выведет 'my_func'.
db_objs['func']()
# Изменяем значение элемента словаря в списке.
db_objs['li'][1][1] = 'один'
# Создаем экземпляр класса.
obj = db_objs['cls']()
# Вызываем его метод. Получим 'MyClass'.
print(obj.get_name())
# Проверим изменения.
with shelve.open(db_dir) as db_objs:
# li => [[1, 2], {1: 'один', 2: 'two'}, 253]
print('li => {}'.format(db_objs['li']))
# Очищаем хранилище.
db_objs.clear()
func => <function my_func at 0x000001D5964504A0>
cls => <class '__main__.MyClass'>
li => [[1, 2], {1: 'one', 2: 'two'}, 253]
my_func
MyClass
li => [[1, 2], {1: 'один', 2: 'two'}, 253]
Пример №8. Использование модуля shelve (часть 3).
Опять же, не стоит без проверки загружать с помощью модулей pickle и shelve файлы из ненадёжных источников, т.к. это может привести к непредвиденным и печальным последствиям в случае передачи злонамеренного кода.
Модуль pathlib в Python: работа с путями
Модуль pathlib представляет собой высокоуровневый инструмент для манипулирования путями файловой системы, по сути объединяя в себе возможности таких модулей, как os, os.path или glob. Ознакомится с документацией по модулю можно в справочнике стандартной библиотеки в подразделе «pathlib — Object-oriented filesystem paths». Здесь же мы рассмотрим наиболее востребованные кроссплатформенные свойства и методы для работы с путями, каталогами и файлами, имеющиеся у класса PurePath и его подкласса Path модуля pathlib.
Объекты pathlib.PurePath() обеспечивают операции обработки пути, которые фактически не обращаются к файловой системе. Поэтому их можно использовать для манипулирования путями Windows на машине Unix или наоборот, не волнуясь при этом за правильную обработку пути. В тоже время подкласс pathlib.Path() представляет пути файловой системы, которые работают с системными вызовами. При создании экземпляра класса pathlib.Path() в зависимости от операционной системы на которой выполняется код, происходит автоматический вызов либо его подкласса pathlib.PosixPath (работа с путями файловой системы, отличной от Windows), либо pathlib.WindowsPath (работа с путями Windows). Перечислим основные методы этих классов по работе с путями и рассмотрим конкретные примеры использования предоставляемых возможностей в исходном коде.
- Path.cwd() – возвращает объект пути, представляющий текущий рабочий каталог (похоже на os.getcwd()).
- Path.home() – возвращает объект пути, представляющий домашний каталог пользователя (похоже на os.path.expanduser()).
- Path(path).expanduser() – возвращает объект абсолютного пути path, в котором пользовательские конструкции ~ или ~user в начале пути заменяются на домашний каталог пользователя (похоже на os.path.expanduser()). Если расширение пути завершается неудачно или путь не начинается с тильды ~, изменения в пути не производятся.
- Path(path).resolve(strict=False) – преобразует относительный путь path в абсолютный. По умолчанию, когда strict=False, преобразование выполняется без проверки существования пути. Однако, если вдоль пути разрешения встречается бесконечный цикл, вызывается исключение RuntimeError. Если используется флаг strict=True и делается попытка преобразования несуществующего пути, интерпретатор возбуждает исключение FileNotFoundError.
- Path(path).samefile(other_path) – сравнивает путь path с путем other_path и возвращает True, если они указывают на один и тот же файл (похоже на os.path.samefile() и os.path.samestat()). Аргумент other_path может быть как объектом pathlib.Path, так и обычной строкой, содержащей путь в абсолютном или относительном выражении (см. пример №9).
# Импортируем модуль.
from pathlib import Path
# Текущий рабочий каталог.
# Вывело D:\python\py_project.
print(Path.cwd())
# Домашний каталог пользователя.
# C:\Users\pi-13.
print(Path.home())
# Замена тильды каталогом пользователя.
# Вывело C:\Users\pi-13/Music.
print(Path('~/Music').expanduser())
# Относительный путь в абсолютный.
# Вывело D:\python\py_project\db_objs.
print(Path('./db_objs').resolve())
# Получаем каталог D:\python\py_project.
p = Path.cwd()
# Сравниваем и везде получаем True.
# Используем абсолютный путь.
print(Path(p).samefile('D:\python\py_project'))
# Текущий каталог через относительный путь.
print(Path(p).samefile('.'))
# Поднялись на каталог выше и зашли обратно.
print(Path(p).samefile('../py_project'))
D:\python\py_project
C:\Users\pi-13
C:\Users\pi-13\Music
D:\python\py_project\db_objs
True
True
True
Пример №9. Использование класса pathlib.Path (часть 1).
- Path(path).exists() – проверяет существование пути path файловой системы, возвращая True или False (похоже на os.path.exists()). Если путь указывает на символическую ссылку, метод проверяет, указывает ли она на существующий файл или каталог.
- Path(path).is_dir() – возвращает True, если путь path указывает на каталог или символическую ссылку, указывающую на каталог (похоже на os.path.isdir(path)). Если путь указывает на файл другого типа, вообще не существует или является неработающей символической ссылкой, метод возвращает False. Также метод может вернуть False, если доступ к директории или символической ссылке, указывающей на директорию, отсутствует.
- Path(path).is_file() – все тоже, что и для метода выше, только для файлов (см. пример №10). Метод похож на os.path.isfile(path).
# Импортируем модуль.
from pathlib import Path
# Текущий рабочий каталог.
# D:\python\py_project.
cur_cat = Path.cwd()
# True.
print(Path(cur_cat).exists())
# True (создан был в нашей базе данных чуть выше).
print(Path('./db_devices/db_devices.dat').exists())
# False (таких файла и каталога нет).
print(Path(str(cur_cat) + '/bad_cat.py').exists())
print(Path(str(cur_cat) + '/bad_cat').exists(), '\n')
# True.
print(Path(cur_cat).is_dir())
print(Path('./db_devices/db_devices.dat').is_file())
print(Path('./db_devices').is_dir(), '\n')
# False.
print(Path('./db_devices').is_file())
print(Path('./db_devices/db_devices.dat').is_dir())
True
True
False
False
True
True
True
False
False
Пример №10. Использование класса pathlib.Path (часть 2).
-
Path(path).mkdir(mode=0o777, parents=False, exist_ok=False) – создает по указанному пути path новый каталог
(похоже на os.mkdir()). Метод принимает следующие аргументы:
- mode=0o777 – режим доступа к директории.
- parents=False – по умолчанию при наличии в цепочке отсутствующих каталогов будет вызвано исключение FileNotFoundError. Если необходимо, чтобы отсутствующие каталоги нового пути были созданы, следует указывать parents=True.
- exist_ok=False – по умолчанию в случае существования целевого каталога будет вызвано исключение FileExistsError. При использовании exist_ok=True исключения игнорируются.
- Path(path).touch(mode=0o666, exist_ok=True) – создает по указанному пути path пустой файл с заданным разрешением. По умолчанию, когда exist_ok=True, функция завершается успешно даже в случае существования файла. При этом время его модификации обновляется до текущего. Если используется exist_ok=False, то в случае существования файла будет вызвано исключение FileExistsError.
- Path(path).unlink(missing_ok=False) – удаляет файл или символическую ссылку, указанную в пути path, при наличии разрешения (похоже на os.unlink()). Если missing_ok=False (по умолчанию) и указанный путь не существует, метод возбуждает исключение FileNotFoundError. Для игнорирования исключения необходимо использовать аргумент с флагом True.
- Path(path).rmdir() – удаляет каталог, указанный в пути path (похоже на os.rmdir()). Каталог должен существовать, быть пустым, а также иметь соответствующие разрешения (см. пример №11).
# Импортируем модуль.
from pathlib import Path
# Текущий рабочий каталог.
# D:\python\py_project.
cur_cat = Path.cwd()
# Класс поддерживает оператор /.
# D:\python\py_project\new_cat.
p = cur_cat / 'new_cat'
# False (каталог еще не создан).
print(Path(p).exists())
# Создаем каталог.
Path(p).mkdir(exist_ok=True)
# Вот теперь имеем True.
print(Path(p).exists(), '\n')
# Создаем в каталоге пустой файл.
Path(p / 'file.txt').touch()
input('Продолжить?')
# Удаляем файл.
Path(p / 'file.txt').unlink()
# Удаляем пустой каталог.
Path(p).rmdir()
print('\nВсе удалено!')
False
True
Продолжить?
Все удалено!
Пример №11. Использование класса pathlib.Path (часть 3).
- Path(path).rename(target) – переименовывает/перемещает файл или конечный каталог пути path в target (похоже на os.rename()). В качестве target разрешается передавать как строку, так и объект пути. Путь path должен существовать, иначе будет брошено исключение. Если путь target уже существует, то в Windows будет брошено исключение, а в Unix путь (файл) будет перезаписан (см. пример №12).
- Path(path).replace(target) – заменяет существующий файл или конечный каталог пути path на target (похоже на os.replace()). В качестве target разрешается передавать как строку, так и объект пути. Путь path должен существовать, иначе будет брошено исключение. Если путь target уже существует, то в Windows будет брошено исключение, а в Unix путь (файл) будет перезаписан. Основное отличие этого метода от Path(path).rename(target) заключается в том, что в ходе замены (переименования) файла данные в нем будут безвозвратно удалены.
# Импортируем модуль.
from pathlib import Path
# Создаем каталог в каталоге скрипта.
# D:\python\py_project\new_cat.
p = './new_cat'
Path(p).mkdir()
# Создаем в каталоге пустой файл.
Path(p + '/file.txt').touch()
input('\nВсе создано!')
# Переименуем каталог и файл.
Path(p).rename('./new_cat_1')
p = './new_cat_1'
Path(p + '/file.txt').rename(p + '/file_1.txt')
input('\nВсе переименовано!')
# Создаем еще один каталог.
# D:\python\py_project\new_cat_2.
Path('./new_cat_2').mkdir()
# Перенесем new_cat_1 вместе с файлом в new_cat_2.
Path('./new_cat_1').rename('./new_cat_2/new_cat_1')
print('\nВсе перемещено!')
Все создано!
Все переименовано!
Все перемещено!
Пример №12. Использование класса pathlib.Path (часть 4).
- Path(path).open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) – открывает файл, на который указывает путь path, и возвращает соответствующий файловый объект для доступа к нему, как это делает встроенная функция open().
- Path(path).write_text(data, encoding=None, errors=None, newline=None) – открывает файл для записи в текстовом режиме, записывает в него данные data и закрывает его, возвращая количество записанных байт. Если файл с таким именем уже существует, он перезаписывается. Необязательные параметры имеют тот же смысл, что и для встроенной функции open().
- Path(path).read_text(encoding=None, errors=None) – открывает, читает и затем закрывает текстовый файл, возвращая прочитанную строку.
- Path(path).write_bytes(data) – открывает файл для записи в бинарном режиме, записывает в него данные data и закрывает его, возвращая количество записанных байт. Если файл с таким именем уже существует, он перезаписывается.
- Path(path).read_bytes() – открывает, читает и затем закрывает двоичный файл, возвращая байтовый объект (см. пример №13).
# Импортируем модуль.
from pathlib import Path
# Создаем каталог в каталоге скрипта.
# D:\python\py_project\file_cat.
p = './file_cat'
Path(p).mkdir()
# Создаем объект пути.
file_obj = Path(p + '/file.txt')
# Создаем в каталоге пустой файл.
file_obj.touch()
# Записываем в него строку.
file_obj.write_text('okpython')
# Открываем, читаем и закрываем файл.
print(file_obj.read_text())
# Открываем файл для дозаписи.
with file_obj.open(mode='a') as f:
# Записываем текст.
f.write('!!!!!')
# Открываем и читаем файл.
with file_obj.open() as f:
print(f.read())
okpython
okpython!!!!!
Пример №13. Использование класса pathlib.Path (часть 5).
- Path(path).glob(pattern) – возвращает список всех файлов, соответствующих заданному шаблону pattern и расположенных в каталоге, указанном в пути path. Начиная с Python 3.11 метод возвращает только каталоги, если шаблон pattern заканчивается разделителем компонентов пути (os.sep или os.altsep). Если в шаблоне использовать комбинацию **, то будет осуществляться рекурсивный обход в глубину каталога, указанного в пути path, и всех его подкаталогов. Это удобно, но в больших деревьях каталогов может занимать слишком много времени.
- Path(path).rglob(pattern) – производит рекурсивный поиск файлов аналогично предыдущему методу за счет автоматического добавления **/ в начале шаблона pattern.
- Path(path).iterdir() – возвращает итератор объектов в каталоге, на который указывает путь path (см. пример №14).
# Импортируем модуль.
from pathlib import Path
# Создаем каталоги.
# D:\python\py_project\dir_1/dir_2.
p = './dir_1/dir_2'
Path(p).mkdir(parents=True)
# Создаем 3 файла.
Path(p + '/f_1.py').touch()
Path(p + '/f_2.py').touch()
Path(p + '/f_1.txt').touch()
# Выведем рез-ты поиска в списках.
print(list(Path(p).glob('*1*.*')))
print(list(Path(p).glob('*.py')))
print(list(Path('./dir_1').glob('*/')))
print(list(Path('.').glob('**/')), '\n')
# Получим итератор и выведем все объекты.
for d in Path('.').iterdir(): print(d)
[WindowsPath('dir_1/dir_2/f_1.py'), WindowsPath('dir_1/dir_2/f_1.txt')]
[WindowsPath('dir_1/dir_2/f_1.py'), WindowsPath('dir_1/dir_2/f_2.py')]
[WindowsPath('dir_1/dir_2')]
[WindowsPath('.'), WindowsPath('dir_1'), WindowsPath('dir_1/dir_2'), WindowsPath('__pycache__')]
dir_1
main.py
__pycache__
Пример №14. Использование класса pathlib.Path (часть 6).
Модуль shutil в Python: операции над файлами и каталогами
Модуль shutil содержит набор высокоуровневых функций для обработки файлов и каталогов, позволяющих, например, копировать, перемещать, удалять и даже архивировать их. Опять же, ознакомится с документацией по модулю можно в справочнике стандартной библиотеки в подразделе «shutil — High-level file operations». Здесь же мы рассмотрим лишь наиболее востребованные кросплатформенные свойства и методы для операций над файлами и каталогами.
- shutil.copyfileobj(fsrc, fdst[, length]) – копирует содержимое файлового объекта fsrc в другой файловый объект fdst. Необязательный параметр length - размер буфера при копировании (используется для предотвращения чтения в память огромных файлов целиком). Следует помнить, что копирование осуществляется с текущей позиции указателя в fsrc, а не с начала файла.
- shutil.copyfile(src, dst, follow_symlinks=True) – копирует содержимое файла src без метаданных в файл dst и возвращает dst. Аргументы src и dst должны быть строками, содержащими пути к файлам. При чем dst должен быть полным именем файла. Если указать существующий файл dst, он будет перезаписан. Если src и dst представляют собой один и тот же файл, будет брошено исключение shutil.SameFileError. Если аргумент follow_symlinks=False, а src является ссылкой на файл, то будет создана новая символическая ссылка вместо копирования файла, на который эта символическая ссылка указывает.
- shutil.copy(src, dst, follow_symlinks=True) – копирует содержимое файла src вместе с правами доступа в файл или каталог dst и возвращает путь к местонахождению нового скопированного файла. Если dst является каталогом, файл будет скопирован со своим прежним именем. Если аргумент follow_symlinks=False, а src – это ссылка, то dst также будет ссылкой. Если же follow_symlinks=True, а src – это ссылка, то dst будет копией файла, на который ссылается src.
- shutil.copy2(src, dst, follow_symlinks=True) – работает как copy(), но пытается копировать вообще все метаданные.
- shutil.copymode(src, dst, follow_symlinks=True) – копирует права доступа из src в dst. Содержимое файла, владелец и группа не затрагиваются.
- shutil.copystat(src, dst, follow_symlinks=True) – копирует права доступа, время последнего доступа и последнего изменения, а также флаги src в dst. Содержимое файла, владелец и группа не затрагиваются.
- shutil.chown(path, user=None, group=None) – меняет владельца и/или группу у файла или каталога.
- shutil.ignore_patterns(*patterns) – создает функцию, которая позволяет выборочно копировать файлы из каталогов, игнорируя файлы и каталоги в соответствии с одним из предоставленных шаблонов *patterns типа glob.glob(). Созданная функция может быть использована в качестве значения аргумента ignore для copytree().
- shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False) – рекурсивно копирует дерево каталогов src в каталог dst и возвращает каталог назначения. Аргумент dirs_exist_ok указывает, следует ли вызвать исключение в случае, если целевой каталог dst или какой-либо отсутствующий родительский каталог уже существует. Права и времена у каталогов копируются с помощью функции copystat(), а файлы по умолчанию копируются с помощью функции copy2(), хотя в аргумент copy_function разрешается передавать любую функцию, поддерживающую такую же сигнатуру, например shutil.copy(). По умолчанию, когда symlinks=False, копируются как содержимое, так и метаданные файлов, на которые указывали ссылки. При этом, если файл, на который указывает ссылка, не существует, в список ошибок в исключении shutil.Error в конце копирования будет добавлено исключение, скрыть которое можно путем установки флага ignore_dangling_symlinks=True. Если аргумент symlinks=True, ссылки в дереве src станут ссылками в dst, а метаданные будут скопированы настолько, насколько это возможно. Если значение аргумента ignore не равно None, то это должен быть объект функции, принимающей в качестве аргументов имя целевого каталога dst и список его содержимого, возвращаемый функцией os.listdir(). Поскольку copytree() вызывается рекурсивно, функция в ignore будет вызываться один раз для каждого вложенного копируемого каталога. Кроме того, переданная в ignore функция должна возвращать последовательность имен каталогов и файлов относительно текущего каталога, то есть подмножества элементов во втором аргументе. Эти имена будут игнорироваться в процессе копирования.
- shutil.move(src, dst, copy_function=copy2) – рекурсивно перемещает файл или каталог из src в другое место dst и возвращает место назначения dst. Если dst является существующим каталогом, то src перемещается внутрь каталога. Если же цель dst существует, но не является каталогом, то она может быть перезаписана.
- shutil.rmtree(path, ignore_errors=False, onerror=None, *, dir_fd=None) – рекурсивно удаляет все дерево каталогов. При этом путь path должен указывать именно на каталог, а не символическую ссылку на каталог. Если указать ignore_errors=True, то возникшие в результате неудачного удаления ошибки будут игнорироваться. По умолчанию, когда ignore_errors=False, такие ошибки вызывают исключения или обрабатываются путем вызова обработчика, если он используется в onerror.
- shutil.which(cmd, mode=os.F_OK | os.X_OK, path=None) – возвращает путь к исполняемому файлу, который будет запущен при вызове командного файла cmd (путь к нему передается в виде строки). Если никакая команда не будет вызвана, функция вернет None. Аргумент mode – это права доступа, требующиеся от файла. По умолчанию проверяется только существование файла и является ли он исполняемым.
- shutil.disk_usage(path) – возвращает статистику использования диска по указанному пути в виде именованного кортежа с атрибутами total, used и free, представляющих осответственно общий объем диска, используемый объем и объем свободного пространства в байтах. Путь path может быть файлом или каталогом (см. пример №15).
# Импортируем модуль shelve.
import shutil, pathlib
# Имена наших тестовых каталогов.
dir_1 = 'dir_1'
dir_2 = 'dir_2'
# Если каталог еще не создан.
if not pathlib.Path(dir_1).exists():
# Создаем его в каталоге скрипта.
pathlib.Path(dir_1).mkdir()
# Создаем и записываем в нем файлы.
pathlib.Path('./dir_1/file_1_1.txt').write_text('1_1')
pathlib.Path('./dir_1/file_1_2.txt').write_text('1_2')
input('dir_1 создан, файлы записаны!')
# Если каталог еще не создан.
if not pathlib.Path(dir_2).exists():
# Создаем его в каталоге скрипта.
pathlib.Path(dir_2).mkdir()
# Создаем и записываем в нем файлы.
pathlib.Path('./dir_2/file_2_1.txt').write_text('2_1')
pathlib.Path('./dir_2/file_2_2.txt').write_text('2_2')
input('dir_2 создан, файлы записаны!')
# Откроем 1-й файл на чтение, а 2-й на запись.
with (open('./dir_1/file_1_1.txt' , 'r') as f_1,
open('./dir_2/file_2_1.txt' , 'w') as f_2):
# Копируем содержимое файлового объекта f_1 в f_2.
# Теперь и там, и там по '1_1' (т.е. перезаписали файл).
shutil.copyfileobj(f_1, f_2)
input('Содержимое file_1_1.txt скопировано в file_2_1.txt!')
# Копируем содержимое file_1_2.txt в file_2_2.txt.
# Теперь и там, и там по '1_2' (т.е. перезаписали файл).
shutil.copyfile('./dir_1/file_1_2.txt', './dir_2/file_2_2.txt')
input('Содержимое file_1_2.txt скопировано в file_2_2.txt!')
# Копируем файлы file_1_1.txt и file_1_2.txt в каталог dir_2.
shutil.copy('./dir_1/file_1_1.txt', './dir_2')
shutil.copy('./dir_1/file_1_2.txt', './dir_2')
input('file_1_1.txt и file_1_2.txt скопированы в dir_2!')
# Удаляем каталог dir_2 вместе с файлами.
shutil.rmtree('./dir_2')
input('Каталог dir_2 удален!')
# Копируем содержимое каталога (будет создан).
shutil.copytree('./dir_1', './dir_3')
input('Содержимое dir_1 скопировано в dir_3!')
# Перемещаем dir_1 в dir_3.
shutil.move('./dir_1', './dir_3')
input('Каталог dir_1 перемещен в dir_3!')
dir_1 создан, файлы записаны!
dir_2 создан, файлы записаны!
Содержимое file_1_1.txt скопировано в file_2_1.txt!
Содержимое file_1_2.txt скопировано в file_2_2.txt!
file_1_1.txt и file_1_2.txt скопированы в dir_2!
Каталог dir_2 удален!
Содержимое dir_1 скопировано в dir_3!
Каталог dir_1 перемещен в dir_3!
Пример №15. Использование модуля shutil (часть 1).
Помимо функций для работы с обычными файлами и каталогами модуль shutil предоставляет также несколько высокоуровневых функций, основанных на возможностях модулей zipfile и tarfile, для создания и чтения архивированных и сжатых файлов. Пробежимся по ним.
-
shutil.make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]]) – создает архивный файл,
например zip или tar и возвращает его имя. Первые два аргумента обязательны для передачи, остальные могут быть
опущены:
- base_name – путь к создаваемому архиву с указанием в конце пути имени файла архива без расширения, т.к. оно зависит от выбранного формата (например, './dir_1/dir_2').
- format – формат создаваемого архива: 'zip', 'tar' или 'gztar', если доступен модуль zlib; bztar, если доступен модуль bz2; xztar, если доступен модуль lzma.
- root_dir – корневой каталог, относительно которого будет строиться путь к архивируемому файлу или каталогу base_dir (по умолчанию используется текущий каталог).
- base_dir – путь к существующему файлу или каталогу, который требуется заархивировать, отсчитываемый относительно корневого каталога root_dir; если путь не указывается, то в архив будет помещено все содержимое каталога root_dir; если указать неверный путь, будет брошено соответствующее исключение.
- verbose – устаревший, больше не используется и не рекомендуется.
- dry_run – если данный аргумент имеет значение True, архив не создается, но выполняемые операции записываются в журнал.
- owner – используется при создании архива tar. По умолчанию используется текущий владелец.
- group – используется при создании архива tar. По умолчанию используется текущая группа.
- logger – данный аргумент должен быть объектом, совместимым с PEP 282, обычно это экземпляр logging.Logger.
- shutil.get_archive_formats() – возвращает список поддерживаемых форматов для архивирования, в котором каждый элемент является кортежем с описанием архиваторов вида (name, description). По умолчанию модуль shutil предоставляет форматы, перечисленные чуть в описании функции shutil.make_archive().
- shutil.unpack_archive(filename[, extract_dir[, format]]) – распаковывает архив filename (необходимо передавать полный путь к архиву). Здесь аргумент extract_dir указывает каталог, в который будет производится распаковка (по умолчанию это текущий каталог), а format – это формат архива (по умолчанию делается попытка угадать формат по расширению файла) (см. пример №16).
- shutil.get_unpack_formats() – возвращает список поддерживаемых форматов для разархивирования, в котором каждый элемент является кортежем с описанием архиваторов вида (name, extensions, description). Опять же, по умолчанию модуль shutil предоставляет форматы, перечисленные выше в описании функции shutil.make_archive().
# Импортируем модули.
import shutil, pathlib
# Дерево тестовых каталогов.
dirs = './dir_1/dir_2'
# Если каталоги еще не созданы.
if not pathlib.Path(dirs).exists():
# Создаем их в каталоге скрипта.
pathlib.Path(dirs).mkdir(parents=True)
# Создаем и записываем файл в dir_1.
p = 'dir_1/f_1_1.txt'
pathlib.Path(p).write_text('1')
# Создаем и записываем файл в dir_2.
p = dirs + '/f_1_2.txt'
pathlib.Path(p).write_text('2')
print('Каталоги созданы, файлы записаны!')
input('Нажмите клавишу для продолжения...')
# Каталог, от которого строим путь к base_dir.
root_dir = './dir_1'
# Архивируем все содержимое dir_1 в my_arch_1.zip,
# расположенный в dir_3 (dir_3 будет создан).
p = './dir_3/my_arch_1'
# Используем формат zip.
shutil.make_archive(p, 'zip', root_dir)
# Архивируем содержимое dir_2 в my_arch_2.zip.
p = './dir_3/my_arch_2'
# Путь к каталогу dir_2 относительно root_dir.
base_dir = 'dir_2'
# Используем формат zip.
shutil.make_archive(p, 'zip', root_dir, base_dir)
# Пытаемся архивировать файл dir_9 в my_arch_3.zip.
p = './dir_3/my_arch_3'
# Имя архивируемого объекта.
base_dir = 'dir_9'
try:
# Используем формат zip.
shutil.make_archive(p, 'zip', root_dir, base_dir)
except Exception as err:
# Выводим ошибку об отсутствии файла.
print('\n{}'.format(err))
# СОобщаем о создании пустого архива.
print('Был создан пустой архив!')
# Сменим путь к каталогу отсчета.
root_dir = './dir_1/dir_2'
# Архивируем файл f_1_1.txt в my_arch_4.zip.
p = './dir_3/my_arch_4'
# Путь к файлу f_1_1.txt относительно root_dir.
base_dir = '../f_1_1.txt'
# Используем формат zip.
shutil.make_archive(p, 'zip', root_dir, base_dir)
print('\nАрхивирование завершено!')
input('Нажмите клавишу для продолжения...')
# Разархивируем, например, my_arch_1.zip в
# dir_4 (dir_4 будет создан).
# Указываем полный путь к архиву.
p = './dir_3/my_arch_1.zip'
shutil.unpack_archive(p, './dir_4')
print('\nmy_arch_1.zip разархивирован!')
input('Нажмите клавишу для продолжения...')
Каталоги созданы, файлы записаны!
Нажмите клавишу для продолжения...
[WinError 2] Не удается найти указанный файл: 'dir_1\\dir_9'
Был создан пустой архив!
Архивирование завершено!
Нажмите клавишу для продолжения...
my_arch_1.zip разархивирован!
Нажмите клавишу для продолжения...
Пример №16. Использование модуля shutil (часть 2).
Краткие итоги параграфа
- Файл – это именованная область данных на носителе информации. При этом большинство операционных систем в целях однозначности не допускает использование двух файлов с полностью идентичными именами в одном каталоге.
- Чтобы помочь системе определить тип файла и, соответственно, приложение для работы с ним, в имени файла обычно указывается его расширение, которое отделяется от остальной части имени точкой. Примерами расширений файлов могут служить .txt – обычный текстовый файл, .py – файл с кодом Пайтона и огромное число других расширений.
- Когда файлов становится слишком много, возникает необходимость их упорядочивания и группировки. Для этих целей используются каталоги или директории, которые представляют собой специальные объекты файловой системы, используемые для упрощения организации файлов. В графическом пользовательском интерфейсе каталоги также называют папками. В любом случае каталоги представляет собой файлы, содержащие записи о входящих в них файлах или других каталогах. При этом каталог, прямо или косвенно включающий в себя все прочие каталоги и файлы программы, называют корневым. А каталог, в котором находится текущий каталог, называют родительским каталогом.
- Для чтения и записи файлов в Python предназначена встроенная функция open(), которая возвращает специальный итерируемый файловый объект, связанный с целевым файлом и обладающий набором методов для чтения, записи и редактирования открытого файла. Сюда относятся, например, методы: read() (читает заданное количество байт из файла), readline() (чтение одной строки), readlines() (читает файл в список строк), write() (записывает строку в файл), writelines() (записывает в файл переданную последовательность строк), close() (закрывает файл).
- Для сериализации и десериализации объектов в Python предназначен модуль pickle стандартной библиотеки. Он позволяет преобразовывать объекты как в поток байтов (сериализация), так и обратно из байтового состояния в объекты (десериализация). А поскольку поток байтов можно легко записывать в файл, данный модуль широко используется для долговременного хранения и последующей загрузки различных сложных python-объектов. Запись объектов в файл осуществляется с помощью функции pickle.dump(), а чтение из файла – pickle.load().
- Если к сохраненным в файлах записям необходим доступ по ключу, вместо модуля pickle удобнее использовать более высокоуровневый инструмент стандартной библиотеки в виде модуля shelve. Данный модуль реализует постоянное хранилище для произвольных python-объектов, значения которого можно извлекать, используя синтаксис и методы обычного словаря со строковыми ключами. Осуществляется открытие такого хранилища с помощью метода shelve.open().
- Для манипулирования путями файловой системы предназначен модуль pathlib, который представляет собой высокоуровневый инструмент объединяющий в себе возможности таких модулей, как os, os.path или glob. С его помощью, например, легко определять текущий рабочий каталог (Path.cwd()), проверять является путь каталогом (Path(path).is_dir()), файлом (Path(path).is_file()) и существует ли он вообще (Path(path).exists()), создавать директории (Path(path).mkdir()) и файлы (Path(path).touch()), а также удалять их (Path(path).rmdir() и Path(path).unlink()), открывать файлы для чтения и записи (Path(path).open()), осуществлять поиск каталогов и файлов по заданному шаблону (Path(path).glob(pattern)) и многое другое.
- Также нами был рассмотрен модуль shutil, содержащий набор высокоуровневых функций для обработки файлов и каталогов. В частности модуль дает возможность копировать содержимое файлов (shutil.copyfileobj() и shutil.copyfile()), копировать сами файлы и каталоги (shutil.copy() и shutil.copytree()), перемещать их (shutil.move()), удалять целые деревья каталогов вместе с файлами (shutil.rmtree()), а также архивировать их (shutil.make_archive() и shutil.unpack_archive()) и т.д.
Вопросы и задания для самоконтроля
1. Что такое файл? Для чего нужны расширения файлов? Показать решение.
Ответ. Файл – это именованная область данных на носителе информации. Имена файлов нужны для того, чтобы точно знать, к какой области данных носителя информации осуществляется запрос. Что касается расширения файла, то оно указывается после точки и призвано помочь системе определить тип файла, а значит и приложение для работы с ним.
2. Для чего нужны каталоги? Какой каталог приложения называют корневым? Какой каталог по отношению к данному является родительским? Показать решение.
Ответ. Каталоги нужны для создания упорядоченной структуры файлов в файловой системе и, в частности, в многофайловой программе. Каталог, прямо или косвенно включающий в себя все прочие каталоги и файлы программы, называется корневым. Родительским называется каталог, в котором находится текущий каталог.
3. Опишите режимы открытия файлов с помощью встроенной функции open(). Показать решение.
Ответ. Режим открытия файла задается аргументом mode, который в качестве строки может принимать следующие значения:
- 'r' – открыть файл на чтение (значение по умолчанию);
- 'w' – открыть файл на запись (содержимое файла удаляется, а если его нет, то создается новый);
- 'x' – создать файл и открыть его на запись либо сгенерировать исключение FileExistsError, если файл с таким именем уже существует;
- 'a' – открыть файл на дозапись (содержимое файла не удаляется, а запись новой информации осуществляется в конец файла);
- 'b' – открыть файл в бинарном режиме; используется как дополнительный флаг, например, 'rb', 'wb' или 'ab';
- 't' – открыть файл в текстовом режиме; используется как дополнительный флаг по умолчанию;
- '+' – открыть файл на чтение и запись одновременно; используется как дополнительный флаг, например, 'r+' или 'w+' (файловый указатель помещается в начало файла, если он существует, либо создается новый файл для чтения и записи), 'a+' (файловый указатель помещается в конец файла, если он существует, либо создается новый файл для чтения и дозаписи), 'rb+', 'wb+' или 'ab+'.
4. Как с помощью встроенной функции open() прочитать небольшой текстовый файл целиком, только первую строку? Показать решение.
Ответ. Необходимо открыть файл в режиме чтения с помощью встроенной функции open(), получив соответствующий файловый объект, после чего использовать метод read() этого файлового объекта для чтения файла целиком или метод readline() для чтения первой строки. В конце нужно не забыть закрыть файл с помощью метода close().
# Создаем в текущей папке скрипта файл test.txt,
# открывая его для записи и сохраняя возвращаемый
# функцией файловый объект в переменной f.
f = open('test.txt', 'w', encoding='utf-8')
# Записываем в него строку.
f.write('okpython . net')
# Не забываем закрыть файловый объект.
f.close()
# Открываем test.txt для чтения.
f = open('test.txt', encoding='utf-8')
# Читаем его целиком.
print(f.read())
# Перемещаем файловый указатель в начало файла.
f.seek(0)
# Читаем и выводим первую строку.
print(f.readline())
# Закрываем файловый объект.
f.close()
okpython . net
okpython . net
5. Как с помощью встроенной функции open() дописать строку в конец файла? Показать решение.
Ответ. Необходимо воспользоваться встроенной функцией open() в режиме дозаписи 'a', получив соответствующий файловый объект, после чего использовать метод write() этого файлового объекта для дозаписи в конец файла требуемой строки. После завершения записи следует закрыть файл с помощью метода close().
6. Перепишите представленный код с использованием менеджера контекста. Показать решение.
f = open('test.txt', 'w', encoding='utf-8')
f.write('okpython . net')
f.close()
f = open('test.txt', encoding='utf-8')
print(f.read())
f.seek(0)
print(f.readline())
f.close()
with open('test.txt', 'w', encoding='utf-8') as f:
f.write('okpython . net')
with open('test.txt', encoding='utf-8') as f:
print(f.read())
f.seek(0)
print(f.readline())
okpython . net
okpython . net
7. Назовите модуль стандартной библиотеки, предназначенный для сериализации и записи объектов в файл, а также для извлечения и десериализации объектов из файла. С помощью каких двух основных функции модуля выполняются данные операции? Показать решение.
Ответ. Для сериализации и десериализации объектов в Python предназначен модуль pickle. Сериализация и запись объектов в файл осуществляется функцией pickle.dump(), а извлечение объектов из файла – функцией pickle.load().
# Импортируем модуль.
import pickle, pathlib
# Пусть, например, у нас есть список.
li = [1, 2, 3]
# Получаем объект пути.
p = pathlib.Path('db_li')
# Если каталог базы данных еще не создан.
if not p.exists():
# Создаем пакет в каталоге скрипта.
p.mkdir()
# Создаем и открываем файл для записи.
with open('db_li/li.pkl', 'wb') as f:
# Сериализуем и записываем в него наш список.
pickle.dump(li, f)
# Открываем файл для чтения.
with open('db_li/li.pkl', 'rb') as f:
# Десериализуем объект и выводим его на экран.
print(pickle.load(f))
[1, 2, 3]
8. Для чего предназначен модуль shelve стандартной библиотеки? Показать решение.
Ответ. Модуль shelve реализует постоянное хранилище для произвольных python-объектов, значения которого можно извлекать, используя синтаксис и методы обычного словаря со строковыми ключами.
9. Перечислите основные режимы открытия хранилища shelve. Показать решение.
Ответ. Для доступа к хранилищу используется метод shelve.open(filename, flag='c', protocol=None, writeback=False), где за режим открытия отвечает параметр flag, который может принимать следующие строковые значения:
- 'c' – хранилище открывается для чтения и записи (используется по умолчанию), а если такого хранилища не существует, то оно вначале создается;
- 'r' – хранилище открывается для чтения;
- 'w' – хранилище открывается для записи;
- 'n' – хранилище открывается для перезаписи, а если такого хранилища не существует, то оно вначале создается.
10. Как получить строку с текущим рабочим каталогом скрипта? Показать решение.
Ответ. Необходимо использовать метод Path.cwd() модуля pathlib или функцию getcwd() модуля os, передав их вызовы в конструктор str. Так, если модули импортированы целиком, инструкции будут иметь вид str(pathlib.Path.cwd()) и str(os.getcwd()).
11. Что делают методы Path(path).exists(), Path(path).is_dir() и Path(path).is_file() модуля pathlib? Показать решение.
Ответ. Path(path).exists() проверяет существование пути path файловой системы; Path(path).is_dir() проверяет, указывает ли путь path на каталог или символическую ссылку, указывающую на каталог; Path(path).is_file() проверяет, указывает ли путь path на файл или символическую ссылку, указывающую на файл.
12. Какие методы класса Path модуля pathlib предназначены для создания директории и файла? Их удаления? Показать решение.
Ответ. Path(path).mkdir(mode=0o777, parents=False, exist_ok=False) – создает по указанному пути path новый каталог; Path(path).touch(mode=0o666, exist_ok=True) – создает по указанному пути path пустой файл с заданным разрешением; Path(path).rmdir() – удаляет каталог, указанный в пути path при наличии разрешения; Path(path).unlink(missing_ok=False) – удаляет файл или символическую ссылку, указанную в пути path, при наличии разрешения.
13. Чем метод open() класса pathlib.Path отличается от встроенной функции open()? Имеются ли у данного класса еще какие-нибудь методы для чтения и записи текстовых и двоичных файлов? Показать решение.
Ответ. В принципе метод pathlib.Path(path).open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) является аналогом встроенной функции open(). В дополнение к нему у класса Path имеется еще пару удобных методов для быстрого чтения и записи текстовых и бинарных файлов: write_text(data, encoding=None, errors=None, newline=None) и read_text(encoding=None, errors=None), а также write_bytes(data) и read_bytes().
14. Можно ли с помощью класса pathlib.Path осуществлять поиск файлов и подкаталогов по шаблону? Показать решение.
Ответ. Да. Для этого предназначен метод Path(path).glob(pattern), который возвращает список всех файлов, соответствующих заданному шаблону pattern и расположенных в каталоге, указанном в пути path. Для рекурсивного поиска без дополнительных настроек подходит метод Path(path).rglob(pattern), а для получения итератора объектов в каталоге – Path(path).iterdir().
15. Как с помощью класса pathlib.Path переименовать файл или каталог? Показать решение.
Ответ. Необходимо использовать метод Path(path).rename(target), который переименовывает (перемещает) файл или конечный каталог пути path в target.
16. В чем заключается основное отличие функции shutil.copyfileobj(fsrc, fdst[, length]) от функции shutil.copyfile(src, dst, follow_symlinks=True). Показать решение.
Ответ. Функция shutil.copyfileobj() работает с файловыми объектами, т.е. файлы должны быть уже открыты. А вот shutil.copyfile() работает с самими файлами, принимая путь к ним в виде строки.
17. Перечислите четыре функции модуля shutil презназначенных для копирования, перемещения и удаления каталогов. Показать решение.
Ответ. shutil.copy(src, dst, ...) – копирует содержимое файла src вместе с правами доступа в файл или каталог dst и возвращает путь к местонахождению нового скопированного файла; shutil.copytree(src, dst, ...) – рекурсивно копирует дерево каталогов src в каталог dst и возвращает каталог назначения; shutil.move(src, dst, ...) – рекурсивно перемещает файл или каталог из src в другое место dst и возвращает место назначения dst; shutil.rmtree(path, ...) – рекурсивно удаляет все дерево каталогов.
18. Как с помощью модуля shutil заархивировать или разархивировать файл или каталог? Показать решение.
Ответ. Для этого в модуле имеются две основных функции: shutil.make_archive() и shutil.shutil.unpack_archive().
19. Дополнительные упражнения и задачи по теме расположены в разделе «Файлы и каталоги» нашего сборника задач и упражнений по языку программирования Python.
Быстрый переход к другим страницам
- Установка библиотек и окружения Python
- Файлы и каталоги в Python
- Дата и время в Python
- К содержанию учебника