Операции с файлами в Python

Операции с файлами в Python

Эта статья объясняет операции с файлами в Python.

В этом руководстве с учётом «безопасности», «эффективности» и «читабельности» поэтапно объясняются концепции — от основ до практических методов.

YouTube Video

Операции с файлами в Python

Работа с файлами — это необходимый базовый навык, который используется как в небольших скриптах, так и в крупных приложениях.

Открытие и закрытие файлов

Сначала рассмотрим примеры чтения и записи текстовых файлов. Использование конструкции with (контекстный менеджер) гарантирует правильное закрытие файла.

Следующий код открывает текстовый файл, читает его содержимое и обрабатывает его построчно.

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • Явное указание encoding="utf-8" помогает снизить количество проблем, зависящих от платформы.
  • Даже для больших файлов цикл for line in f эффективно использует память.

Запись (перезапись и добавление)

Обращайте внимание на режим при записи в файлы. w — для перезаписи, a — для добавления. Используйте конструкцию with и при записи.

Следующий код показывает базовые примеры перезаписи и добавления.

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • Этот код записывает текст в output.txt, а затем добавляет ещё текст в тот же файл. Режим "w" перезаписывает файл, а режим "a" добавляет новую строку в конец существующего содержимого.
  • Если нужно сбросить буфер, вызовите flush(), но обычно содержимое автоматически сбрасывается при завершении контекста выполнения.
  • Если несколько процессов или потоков могут одновременно записывать данные, нужно учитывать механизм эксклюзивного контроля, например, блокировку файлов.

Чтение и запись двоичных данных

Изображения и сжатые файлы обрабатываются в двоичном режиме (rb или wb). В отличие от текстового режима, параметр encoding игнорируется.

Следующий код читает двоичный файл и копирует его в другой файл в двоичном режиме.

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • При работе с большими двоичными файлами избегайте чтения всего файла с помощью read(), используйте чтение и запись порциями для лучшей экономии памяти.

Пример обработки больших файлов порциями

Для очень больших файлов, не помещающихся в памяти, читайте и записывайте их частями фиксированного размера. Так как процесс зависит от ввода-вывода, полезно регулировать размер буфера.

Следующий код копирует файл порциями по 64КБ. Это обеспечивает быструю работу при низком использовании памяти.

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • Вы можете изменить размер порции в зависимости от характеристик вашей сети или диска. Для SSD зачастую лучше использовать чуть большие размеры порции.

Пример использования аргумента buffering

Указав аргумент buffering в функции open(), вы можете управлять размером буфера, используемого внутренне Python. Это позволяет дополнительно оптимизировать эффективность ввода/вывода.

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • Если установить значение buffering в 0, ввод/вывод осуществляется без буферизации; 1 включает построчную буферизацию, а значения 2 и выше используют буфер указанного размера в байтах.
  • В общем случае, значение по умолчанию достаточно, поскольку операционная система эффективно обрабатывает кэширование, но настройка этого параметра может быть полезна для очень больших файлов или специальных устройств.

Современные операции с файлами с использованием Pathlib

Стандартный модуль pathlib делает работу с путями более интуитивно понятной. Это повышает читаемость и надёжность по сравнению с использованием строк для путей.

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • Этот код демонстрирует создание директории с помощью pathlib, запись в текстовый файл и чтение его содержимого. С объектом Path вы можете работать с путями интуитивно и безопасно.
  • Path предлагает удобные API, такие как iterdir() и glob(). Вы можете писать код, не заботясь о разделителях путей между разными операционными системами.

Временные файлы и директории (tempfile)

Временные файлы можно безопасно создавать с помощью tempfile. Это помогает избежать гонки безопасности и конфликтов имён.

Следующий код показывает пример создания временных данных с использованием временного файла.

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • Этот код создаёт временный файл, записывает и читает данные, а затем автоматически удаляет файл после завершения блока with. С помощью tempfile.NamedTemporaryFile вы можете безопасно работать с временными файлами и избегать конфликтов. Поскольку задано delete=True, файл удаляется автоматически.
  • В Windows вы можете не суметь открыть файл из другого дескриптора сразу, поэтому можно использовать delete=False и управлять удалением вручную.

shutil: высокоуровневые операции копирования, перемещения и удаления

С помощью shutil проще выполнять рекурсивное копирование, перемещение и удаление файлов и директорий.

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 также копирует метаданные (например, время изменения). move выполнит перемещение даже в тех случаях, когда нельзя использовать переименование.
  • rmtree — опасная операция, поэтому всегда подтверждайте и делайте резервную копию перед удалением.

Метаданные файлов (os.stat) и работа с правами доступа

Размер файла, время изменения и права доступа можно читать и изменять с помощью os и stat.

Следующий код получает базовую информацию о файле через os.stat и изменяет права доступа с помощью os.chmod.

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • Права доступа работают по-разному в системах POSIX и Windows. Если важна кроссплатформенная совместимость, используйте высокоуровневые API или добавьте условную обработку.

Блокировка файлов (эксклюзивный доступ) — различия между Unix и Windows

Эксклюзивный контроль необходим при параллельном доступе нескольких процессов к одному файлу. В UNIX используется fcntl, в Windows — msvcrt.

Следующий код использует fcntl.flock в UNIX-системах для получения эксклюзивной блокировки при записи.

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • Этот код захватывает эксклюзивную блокировку с помощью fcntl.flock на UNIX-подобных системах, чтобы безопасно записать и предотвратить одновременную запись в файл. Всегда освобождайте блокировку после завершения обработки, чтобы другие процессы получили доступ к файлу.
  • В Windows используйте msvcrt.locking(). Для более высокоуровневого использования рассмотрите сторонние библиотеки, такие как portalocker или filelock.

Атомарные шаблоны записи файлов

Чтобы избежать повреждения файла при обновлении, запишите данные во временный файл и замените основной с помощью os.replace в случае успеха.

Запись во временный файл с последующей заменой помогает избежать повреждений в случае сбоя.

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

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

Быстрый доступ с помощью mmap (для больших объёмов данных)

Для произвольного доступа к большим файлам mmap повышает производительность ввода-вывода. Он в основном используется для двоичных операций.

Следующий код отображает файл в память и читает или записывает определённые диапазоны байт. Будьте осторожны при изменении размера файла.

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • Этот код отображает двоичный файл в память с помощью mmap и выполняет прямые операции чтения/записи на уровне байтов. Отображение файла в память обеспечивает быстрый произвольный доступ к большим наборам данных.
  • mmap эффективен, но неправильное применение может привести к проблемам с целостностью данных. Вызывайте flush() для синхронизации при необходимости.

CSV / JSON / Pickle: чтение и запись по формату

Для определённых форматов данных существуют специализированные модули. Используйте csv для CSV, json для JSON и pickle для сохранения объектов Python.

Следующий код содержит основные примеры чтения и записи CSV и JSON, а также использования Pickle. Так как Pickle может выполнять произвольный код, избегайте загрузки данных из ненадёжных источников.

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • Рекомендуется указывать newline="" для CSV, чтобы избежать лишних пустых строк в Windows. С параметром ensure_ascii=False JSON сохраняет читаемость символов в UTF-8.

Прямая работа с сжатыми файлами (gzip / bz2 / zipfile)

Прямая работа с gzip и zip позволяет экономить место на диске. В стандартную библиотеку входят соответствующие модули.

Следующий код — простой пример чтения и записи файлов gzip как текста.

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • Существует компромисс между степенью сжатия и скоростью в зависимости от уровня сжатия и формата.

Меры по обеспечению безопасности и устранению уязвимостей

Следующие моменты можно учитывать при оценке мер безопасности и уязвимостей.

  • Не используйте недоверенные данные напрямую в именах файлов или путях.
  • Используйте Pickle только с надёжными источниками.
  • Сведите к минимуму права выполнения и предоставляйте процессам только минимально необходимые разрешения при работе с файлами.
  • Используйте временные файлы через tempfile и не сохраняйте обычные файлы в общедоступных каталогах.

Если вы используете пользовательский ввод в путях к файлам, требуется нормализация и проверка данных. Например, используйте Path.resolve() и проверяйте родительские директории.

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • Будьте особенно осторожны при использовании внешних данных в качестве путей к файлам в веб-приложениях или публичных API.

Сводка распространённых шаблонов

  • Всегда используйте конструкцию with (автоматическое закрытие).
  • Явно указывайте параметр encoding для текстовых данных.
  • Читайте и записывайте большие файлы порциями.
  • Внедрите блокировку файлов для совместно используемых ресурсов.
  • Для критических обновлений используйте атомарный шаблон: «записывать во временный файл → os.replace».
  • Всегда подтверждайте и создавайте резервные копии перед выполнением опасных операций (например, удаление или перезапись).
  • Нормализуйте и проверяйте данные при использовании внешнего ввода в качестве путей к файлам.

Резюме

Для работы с файлами важно использовать безопасные и надежные методы, такие как оператор with, явное указание кодировки и атомарная запись. Для крупномасштабной обработки или параллельного доступа необходимо реализовать системы блокировки и управления журналами для предотвращения повреждения данных и конфликтов. Баланс между эффективностью и безопасностью — ключ к надежной работе с файлами.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video