Обработка данных. Часть 2#
Работа с файловой системой#
Пути к файлам#
В Windows пути:
Начинаются с имени диска (
C:
,D:
, …)Регистронезависимы (
Hello.txt
иhello.txt
один файл)Компоненты пути разделяются символом
\
В POSIX системах пути:
Начинаются с корневой директории
/
Регистрозависимы (
Hello.txt
иhello.txt
разные файлы)Компоненты пути разделяются символом
/
Пути бывают:
Абсолютные (
C:\python\python.exe
,/usr/bin/python
)Относительные (
..\python.exe
,../python
)Со специальными сокращениями
%WINDIR%\notepad.exe
,~/file.txt
Работа с путями как со строками#
Пути к файлам с точки зрения системы это текстовые строки.
filepath = r"C:\Windows\notepad.exe"
filepath = "/usr/bin/nano"
Просто
Всегда работает
Неперенасимо
Работа с путями как с объектами#
from pathlib import Path
p = Path("/usr/lib/libm.a")
print(p.parts)
print(p.parents[0])
print(p.parents[1])
('/', 'usr', 'lib', 'libm.a')
/usr/lib
/usr
Сложнее
Иногда приходится приводить к строкам
Переносимо
Некоторые функции#
Основные модули для работы с путями:
import os
import os.path
import shutil
Текущая папка скрипта#
os.chdir("/usr/lib")
print(os.getcwd())
/usr/lib
Пути абсолютные и относительные#
p1 = os.path.relpath("/usr/lib/libm.a")
print(p1)
p2 = os.path.relpath("/usr/bin/bash")
print(p2)
p3 = os.path.abspath('../../usr/bin/bash')
print(p3)
libm.a
../bin/bash
/usr/bin/bash
Компоненты пути#
# Имя файла без пути
p4 = os.path.basename("/usr/lib/libm.a")
print(p4)
# Путь без имени
p5 = os.path.dirname("/usr/lib/libm.a")
print(p5)
# Компоненты пути
p6 = os.path.normpath("/usr/lib/libm.a").split(os.sep)
print(p6)
# Расширение файла (последнее)
_, p7 = os.path.splitext("/usr/lib/libm.a")
print(p7)
libm.a
/usr/lib
['', 'usr', 'lib', 'libm.a']
.a
Существование файлов и папок#
# Существование файла
s1 = os.path.isfile("/usr/lib/libm.a")
print(s1)
# Существование папки
s2 = os.path.isdir("/usr/lib")
print(s1)
True
True
Атрибуты файлов и папок#
# Время создания
t2 = os.path.getctime("/usr/lib/libm.a")
print(t2)
# Время изменения
t3 = os.path.getmtime("/usr/lib/libm.a")
print(t3)
# Размер в байтах
s = os.path.getsize("/usr/lib/libm.a")
print(s)
1724592400.795275
1722888052.0
98
Перемещение/копирование/удаление#
# Создание одной папки
os.mkdir("/tmp/test")
# Создание всех папок в пути
os.makedirs("/tmp/test/test/test")
# Копирование папки рекурсивно
shutil.copytree("/tmp/test", "/tmp/test2")
# Удаление папки (пустой)
os.rmdir("/tmp/test/test/test")
# Удаление папки с файлами рекурсивно (осторожно!)
shutil.rmtree("/tmp/test")
shutil.rmtree("/tmp/test2")
with open('/tmp/test.txt', 'w') as f:
f.write('test')
# Перемещения файла или папки
shutil.move("/tmp/test.txt", "/tmp/test2.txt")
# Копирование файла
shutil.copy("/tmp/test2.txt", "/tmp/test3.txt")
# Удаление файла
os.remove("/tmp/test2.txt")
os.remove("/tmp/test3.txt")
Список файлов по маске#
Обход по маске — метод glob модуля glob.
import glob
import os
s1 = glob.glob("/usr/lib/*.a")
print(s1[0], '...', s1[-1])
s2 = glob.glob("/usr/lib/**/*.a")
print(s2[0], '...', s2[-1])
/usr/lib/libllvm_gtest_main.a ... /usr/lib/libQt6QmlTypeRegistrar.a
/usr/lib/tdbc1.1.7/libtdbcstub1.1.7.a ... /usr/lib/gprofng/libgp-collectorAPI.a
Символ маски |
Значение |
---|---|
|
Любое число любых символов |
|
Один любой символ |
|
Обойти все папки рекурсивно |
Обход файлов#
Рекурсивный обход файлов метод walk
модуля os
.
import os
for dirname, subdirs, files in os.walk("/usr/lib"):
# dirname — имя текущей папки
print(dirname)
# subdirs — подпапки текущей папки
print(subdirs[0], '...', subdirs[-1])
# files — файлы в текущей папке
print(files[0], '...', files[-1])
break # останавливаем обход принудительно
/usr/lib
tc ... gssproxy
libQt5Charts.so.5.15 ... libjasper.so.7.0.0
Что еще нужно знать о путях#
Если путь не абсолютный, то он вычисляется от текущей папки программы.
Обход глубоко вложенных деревьев может занимать очень много времени.
Разные файловые системы (ФС) имеют различные особенности в частности атрибуты и альтернативные потоки.
В Windows и POSIX различная схема управления правами на доступ к файлам, а еще есть такие вещи, как SELinux и ACL.
Минимальный размер файла и папки ~ 4 КБ (зависит от ФС).
Можно получать уведомления об изменении файлов от ОС, но это платформозависимо (модули
inotify
иwatchdog
).
Работа с файлами#
В зависимости от того, с какими опциями открыт файл в Python он может быть или текстовым (из него будут читаться строки str
) или двоичным (из него будут читаться байтовые строки bytes
).
Структура текстового файла:
Hello world!
Привет мир!
Машинное представление:
00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a d0 9f d1 |Hello world!....|
00000010 80 d0 b8 d0 b2 d0 b5 d1 82 20 d0 bc d0 b8 d1 80 |......... ......|
00000020 21 0a |!.|
Кодировки#
Кодировки бывают:
Однобайтовые (кодовые страницы, например,
CP1251
): один байт соответствует одному символу. Диапазон 0-127 одинаков для всех кодовых страниц и включает символы латинского алфавита, знакам препинания, цифры, основные знаки математических действий некоторые непечатные символы. Диапазон 127-255 свой для каждой кодировки.Многобайтовые (Unicode)
С фиксированной шириной (например,
UTF-32
): 2, 4 или 8 байт на каждый символ.С плавающей шириной (например,
UTF-8
,UTF-16
): количество байтов на символ, зависит от символа.
Популярные кодировки:
ascii
— текстовые файлы IBM, только латиница.cp1251
— текстовые файлы в русской локализации Windows.cp866
— текстовые файлы в русской локализации DOS.utf8
— стандарт для современных Unix-like систем. Кодировка текстовых файлов в Python по умолчанию. Кодировка исходного кода на Python по умолчанию.
Еще Windows, Unix и Mac отличаются символами конца строки в текстовых файлах:
Windows:
\n\r
Unix:
\n
Mac:
\r
Работа с текстовыми файлами#
Открытие и закрытие файла#
# Создание файла
with open('/tmp/test.txt', 'w') as f:
f.write('test hello world')
# Открытие на чтение (текст)
fobj = open("/tmp/test.txt")
# Открытие на чтение в заданной кодировке (текст)
fobj = open("/tmp/test.txt", encoding="utf8")
fobj = open("/tmp/test.txt", encoding="utf8", newline="\n")
# Работа с файлом
# Закрытие
fobj.close()
Преимущества: простота.
Проблема: если при работе с файлом возникнет исключение, то файл может останется незакрытым до завершения работы программы.
Работа с файлом через with#
with open("/tmp/test.txt") as fobj:
pass
# Работа с файлом
Преимущества: если при работе с файлом возникнет исключение, то он все равно будет закрыт.
Проблема: рост уровня вложенности.
Чтение файла#
fobj = open("/tmp/test.txt")
# Прочесть 5 байт
s = fobj.read(5)
print(s)
# Прочесть еще 5 байт
s = fobj.read(5)
print(s)
# Прочесть файл до конца
s = fobj.read()
print(s)
fobj.close()
test
hello
world
Чтение в список строк#
fobj = open("/tmp/test.txt")
# Прочесть файл
sa = fobj.readlines()
# sa == ['Hello world!\n','Привет мир!\n']
fobj.close()
Преимущества: Можно обращаться к отдельным строкам.
Недостатки: Файл приходится целиком загружать в память.
Чтение файла построчно#
fobj = open("/tmp/test.txt")
for line in fobj:
print(line)
fobj.close()
test hello world
Преимущества: В каждый момент времени в память загружена только одна строка.
Недостатки: Некоторые алгоритмы обработки файлов трудно реализовать.
Перемещение по файлу#
По любому файлу (текстовому или двоичному) можно перемещаться по номеру байта. (осторожнее с Unicode, если номер окажется посередине многобайтового символа — будет ошибка).
fobj = open("/tmp/test.txt")
s = fobj.read()
print(s)
#Получить текущее положение в файле (в байтах)
ps = fobj.tell()
print(ps)
#Перейти на заданное положение в файле (в байтах)
fobj.seek(13)
s = fobj.read(5)
print(s)
# Перейти в начало
fobj.seek(0)
s = fobj.read()
print(s)
fobj.close()
test hello world
16
rld
test hello world
Открытие файла на запись#
По умолчанию файлы открываются только на чтение.
Для записи в файл его нужно открыть с соответствующей опцией (второй аргумент open
), которая по традиции обозначается буквой. Если файл не существует, он будет создан, если существует, то возможны варианты.
Опция |
Режим |
Текущая позиция чтения/записи |
---|---|---|
пусто или |
Только чтение |
начало |
|
Запись, файл обрезается до пустого |
начало |
|
Чтение и запись, файл не обрезается |
начало |
|
Запись, файл не обрезается |
конец |
|
Чтение и запись, файл не обрезается |
конец |
Запись в файл#
fobj = open("/tmp/test.txt", "w")
wbc = fobj.write("Hello world!\n")
# wbc == 13
fobj.close()
fobj = open("/tmp/test.txt", "r+")
content = fobj.read()
wbc = fobj.write("Hello world!\n")
# wbc == 13
wt = fobj.tell()
# wt == 26
fobj.close()
Двоичные файлы#
Работа с двоичным файлом аналогична работе с текстовым, однако меняются опции открытия файла. И читать/писать придется объекты типа bytes
, а не str
.
Опция |
Режим |
Текущая позиция чтения/записи |
---|---|---|
|
Только чтение |
начало |
|
Запись, файл обрезается до пустого |
начало |
|
Чтение и запись, файл не обрезается |
начало |
|
Запись, файл не обрезается |
конец |
|
Чтение и запись, файл не обрезается |
конец |
Чтение и запись#
fobj = open("/tmp/test.txt", "ab+")
ds = "Привет мир!\n"
dbytes = ds.encode('utf8')
print(dbytes)
wbc = fobj.write(dbytes)
print(wbc)
fobj.seek(0)
dbs = fobj.read(24)
print(dbs)
fobj.close()
os.remove("/tmp/test.txt") # Очистка
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82 \xd0\xbc\xd0\xb8\xd1\x80!\n'
21
b'Hello world!\nHello world'
Чтение и запись Python объектов#
Самый простой способ сохранить структурированные данные в Python — воспользоваться модулем pickle
стандартной библиотеки.
import pickle
sdata = {'keyA': [1, 2, 3],
'keyB': ("строка", b"bytes"),
'keyC': 'Тест' }
with open('/tmp/data.pickle', 'wb') as fobj:
pickle.dump(sdata, fobj)
with open('/tmp/data.pickle', 'rb') as fobj:
tdata = pickle.load(fobj)
print(tdata)
os.remove("/tmp/data.pickle") # Очистка
{'keyA': [1, 2, 3], 'keyB': ('строка', b'bytes'), 'keyC': 'Тест'}
Достоинства: Простота. Сохраняет практически любой Python объект.
Недостатки: Только для Python. Небезопасно с точки зрения обмена данными.
Чтение и запись числовых массивов#
Для работы с массивами чисел в заданном машинном формате в Python есть класс array
.
import array
ar = array.array('f', [2.0, 4.5, 3.3])
with open('/tmp/array.dat', 'wb') as fobj:
ar.tofile(fobj)
ar2 = array.array('f')
with open('/tmp/array.dat', 'rb') as fobj:
ar2.fromfile(fobj, 3)
print(ar2)
os.remove("/tmp/array.dat") # Очистка
array('f', [2.0, 4.5, 3.299999952316284])
Некоторые форматы#
Формат |
C-тип |
Python-тип |
Размер элемента в байтах |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Подробная таблица форматов.
Достоинства: Простота. Переносимость.
Недостатки: Можно сохранять только массивы чисел.
Чтение и запись С-структур#
Для работы со структурами данных C в Python есть модуль struct.
import struct
st = struct.Struct('10sIIxBB')
data = st.pack(b"Hello",2,3,19,19)
# data == b'Hello\x00\x00\x00\x00\x00...
with open('/tmp/struct.dat', 'wb') as fobj:
fobj.write(data)
with open('/tmp/struct.dat', 'rb') as fobj:
td = fobj.read()
values = st.unpack(td)
print(values)
(b'Hello\x00\x00\x00\x00\x00', 2, 3, 19, 19)
Если структура данных сложная, но есть ее описание на С, то можно воспользоваться модулем cffi
.
Некоторые форматы struct#
Управление порядком байт <
— little-endian, >
— big-endian.
Формат |
C-тип |
Python-тип |
Размер элемента в байтах |
---|---|---|---|
|
— |
— |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Подробная таблица форматов.
Достоинства: Переносимость.
Недостатки: Нужно вручную описывать формат структур, думать о порядке байт и выравнивании.
Файлы отображенные в память#
Есть возможность работать с файлом, как с массивом байтов не загружая его в память полностью. Для этого используется отображения файла в память.
import mmap
fobj = open("/tmp/struct.dat", "r+b")
# Создаем отображенный файл:
# Первый аргумент - дескриптор заранее открытого файла
# Второй - начальное положение в байтах (можно отобразить не весь файл)
mapobj = mmap.mmap(fobj.fileno(), 0)
# Работаем с файлом как с массивом байтов
print(mapobj[:5])
mapobj[2:5] = b"***"
mapobj.close()
fobj.close()
with open("/tmp/struct.dat", "rb") as fobj:
print(fobj.read())
os.remove("/tmp/struct.dat") # Очистка
b'Hello'
b'He***\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x13\x13'
Также файл отображенный на память поддерживает стандартные методы для работы с файлами, напрмер read
и write
.
Псевдофайлы#
С точки зрения Python файлы это просто объекты имеющие определенные методы.
В Python есть классы объекты которых выглядят, как утки файлы, но файлами не являются. Например StringIO
и BytesIO
из модуля io
стандартной библиотеки.
import io
fobj = io.StringIO()
fobj.write('First line.\n')
fobj.seek(5)
s = fobj.read()
print(s)
ss = fobj.getvalue()
print(ss)
fobj.close()
line.
First line.
Сводная таблица методов файлов#
Метод |
Действие |
Возвращает |
---|---|---|
|
Читает файл от текущей позиции до конца |
|
|
Читает n символов или байт |
|
|
Читает текстовый файл от текущей позиции до конца, разбивает результат по |
|
|
Записывает в файл с текущей позиции |
|
|
Текущее положение в файле всегда в байтах |
|
|
Перемещается на позицию n в файле всегда в байтах, возвращает позицию на которую удалось переместиться |
|
|
Записывает сбрасывает буфер на диск |
|
|
Закрывает файл |
|
Особые файлы#
При запуске скрипта автоматически открываются 3 файла доступных в модуле sys
:
Файл |
Тип |
Смысл |
---|---|---|
|
|
вывод в консоль |
|
|
вывод в консоль |
|
|
сообщения об ошибках |
|
|
сообщения об ошибках |
|
|
ввод с консоли |
|
|
ввод с консоли |
Такие операции как print
, input
сводятся к работе с этими файлами.
Что еще нужно знать о файлах#
Как правило
read
/write
— слишком низкий уровень.По умолчанию запись в файл производится не в момент
write
, а в момент, когда накопится достаточно данных для записи. Для того, чтобы выполнить запись немедленно есть методflush
.Если файл больше десятка мегабайт, загружать его в память целиком — не очень хорошая идея.
При работе с двоичными файлами, помните, что есть
byteorder
.Будьте осторожны с записью и перезаписью файлов.