Работа с файлами в Python

Содержание
Введение
Создать
open(): Открыть
close(): Закрыть
with: Менеджер контекста
read(): чтение файла
seek(0): перемещение в начало файла
readline(): построчное чтение
Очистить файл
Копировать файл
Запись в файл
Дописать к файлу
Записать json в файл
Удалить первые несколько строк файла
Запись вывода программы в файл
Пример
Определить кодировки файлов
Прочитать файл из другой директории
glob
Похожие статьи

Введение

В этой статье вы узнаете как организовать работать с файлами в Python 3.

Создайте файл files.py и копируйте туда код из примеров.

Запустить файл можно командой

python3 files.py

Создать

Создать файл можно командой open

Опции:

r  чтение

rb чтение в бинарном режиме

rt чтение в текстовом режиме

только запись.

wb запись в бинарном режиме

wt запись в текстовом режиме

w+ запись и чтение

запись в конец файла - сохранит данные, которые были в файле

b - это селектор бинарного режима

t - это селектор текстового режима

Любая опция с w перезапишет существующий файл - будьте внимательны!

Пример:

f = open("log.txt","w+")

Кодировка

docs.python.org/3/library/codecs.html#standard-encodings

import sys print(sys.getdefaultencoding())

utf-8

Открыть файл

Синтаксис:

open(path_to_file, mode, encoding)

По умолчанию используется кодировка utf-8

Чтобы открыть файл для чтения выполните

f = open("log.txt","r")

Если файл log.txt не существует, он не будет создан

raceback (most recent call last): File "files.py", line 1, in <module> f = open('log.txt', 'r') FileNotFoundError: [Errno 2] No such file or directory: 'log.txt'

Закрыть файл

Чтобы закрыть файл выполните

f.close()

Менеджер контекста

Предпочтительнее использовать менеджер контекста для работы с файлами

Как только вы выйдете из блока в котором открыт файл - он автоматически закроется

with open('text.txt', 'r') as f: pass print(f.closed)

True

Будем рассматривать примеры работы с файлами как с использованием менеджера контекста так и без него.

read(): чтение файла

Получать данные из файла можно с помощью read()

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

Прочитать содержимое файла

with open('sites.txt', 'r') as f: f_contents = f.read() print(f_contents)

www.urn.su www.heihei.ru www.topbicycle.ru

Рассмотрим файл sites.md который состоит из одной строки

topbicycle.ruheihei.rueht1.ru

>>> f = open('sites.md', mode='rt', encoding='utf-8') >>> f.read(3)

'top'

>>> f.read(7)

'bicycle'

>>> f.read(3)

'.ru'

Чтобы получить всё что осталось в файл нужно вызвать read() без аргументов.

>>> f.read()

'heihei.rueth1.ru'

Если вызвать read() ещё раз, вернётся пустая строка

>>> f.read()

''

В данный момент указатель стоит на конце файла, но его можно переместить с помощью seek()

seek(0): перемещение в начало файла

С помощью seek(0) можно поставить указатель в начало файла.

Перейдём в конец файла sites.md

topbicycle.ruheihei.rueht1.ru

>>> f = open('sites.md', mode='rt', encoding='utf-8') >>> f.read(3)

top

>>> f.read()

'bicyleheihei.rueth1.ru'

>>> f.read()

''

Теперь с помощью seek() поставим указатель в начало

>>> f.seek(0)

0

>>> f.read(3)

'top'

Очистить файл

>>> f = open('sites.md', mode='w', encoding='utf-8') >>> f.write('')

0

>>> f.close() >>> exit()

readline(): построчное чтение

Метод readline() выводит содержимое построчно.

А метод readlines() выводит все строки в виде списка

>>> f = open('sites.md', mode='rt', encoding='utf-8') >>> f.readline()

'www.topbicycle.ru\n'

>>> f.readline()

'www.heihei.ru\n'

>>> f.readline()

'eth1.ru'

>>> f.readline()

''

>>> f.seek(0)

0

>>> f.readlines()

['www.topbicycle.ru\n', 'www.heihei.ru\n', 'eth1.ru']

>>> f.close()

Все строки с символом перехода на новую строку - readlines

with open('sites.txt', 'r') as f: f_contents = f.readlines() print(f_contents)

['www.urn.su\n', 'www.heihei.ru\n', 'www.topbicycle.ru\n']

Строки по одной readline

with open('sites.txt', 'r') as f: f_contents = f.readline() print(f_contents) f_contents = f.readline() print(f_contents)

www.urn.su www.heihei.ru

Строки по одной без лишних переходов end=''

with open('sites.txt', 'r') as f: f_contents = f.readline() print(f_contents, end = '') f_contents = f.readline() print(f_contents, end = '')

www.urn.su www.heihei.ru

Цикл для построчного вывода

with open('sites.txt', 'r') as f: for line in f: print(line, end = '')

www.urn.su www.heihei.ru www.topbicycle.ru

В качестве альтернативы можно использовать sys.stdout.write()

import sys f = open(sys.argv[1], mode='rt', encoding='utf-8') for line in f: sys.stdout.write(line) f.close()

python files.py sites.txt

www.urn.su www.heihei.ru www.topbicycle.ru

Прочитать определённое количество символов

with open('sites.txt', 'r') as f: f_contents = f.read(20) print(f_contents)

www.urn.su www.heihe

Если выполнять эту команду последовательно - будут прочитаны следующие символы

with open('sites.txt', 'r') as f: f_contents = f.read(20) print(f_contents, end = '') f_contents = f.read(20) print(f_contents, end = '')

www.urn.su www.heihei.ru www.topbicycle.

Цикл для произвольного количества символов .read

with open('sites.txt', 'r') as f: size_to_read = 10 f_contents = f.read(size_to_read) while len(f_contents) > 0: print(f_contents, end = '') f_contents = f.read(size_to_read)

www.urn.su www.heihei.ru www.topbicycle.ru

Выражение f_contents = f.read(size_to_read) нужно для того, чтобы когда файл закончится и f.read(size_to_read) станет нулем len(f_contents) тоже станет нулем и цикл завершится

Имя файла

Пример программы, которая выводит на экран имя файла и режим, в котором он открыт

f = open('log.txt', 'r') print(f.name) print(f.mode) f.close()

Если файл log.txt существует, то в терминале вы увидите

log.txt
r

Копирование файлов

Текстовые файлы

Их можно копировать построчно

with open('sites.txt', 'r') as rf: with open('sites_copy.txt', 'w') as wf: for line in rf: wf.write(line)

cat sites_copy.txt

www.urn.su www.heihei.ru www.topbicycle.ru

Изображения

Их тоже можно копировать построчно, но открывать и записывать нужно в побитовом режиме. То есть нужно добавлять опцию b

Скачайте изображение велосипеда с сайта TopBicycle.ru или возьмите любую другую картинку

wget https://topbicycle.ru/b/img/stels_pilot_950_MD_26.jpg
ls

stels_pilot_950_MD_26.jpg

with open('stels_pilot_950_MD_26.jpg', 'rb') as rf: with open('stels_pilot_950_MD_26_copy.jpg', 'wb') as wf: for line in rf: wf.write(line)

python3 files.py
ls

stels_pilot_950_MD_26.jpg stels_pilot_950_MD_26_copy.jpg

Более правильным подходом считается копирование не в построчном режиме а частями с фиксированным размером

with open('stels_pilot_950_MD_26.jpg', 'rb') as rf: with open('stels_pilot_950_MD_26_copy.jpg', 'wb') as wf: chunk_size = 4096 rf_chunk = rf.read(chunk_size) while len(rf_chunk) > 0: wf.write(rf_chunk) rf_chunk = rf.read(chunk_size)

Записать файл

Чтобы очистить файл от старого содержимого и записать в него новое используется опция w (write)

with open('log.txt', 'w') as f: f.write("some text")

Рассмотрим запись в файл в интерактивном режиме и без менеджера контекста

>>> f = open('sites.md', 'w') >>> f.write("topbicycle.ru")

13

>>> f.write("heihei.ru")

9

13 и 9 это число байт переданное в файл

>>> f.close() >>> exit() cat sites.md

cat sites.md www.topbicycle.ru www.heihei.ru eth1.ruwww.aviasales.ru www.booking.comwww.tutu.ru www.velodrive.ruwww.velosklad.ru%

topbicycle.ruheihei.ru

ls -l

-rw-r--r-- 1 andrei users 25 Apr 30 07:16 sites.md

Конечный размер файла будет зависеть от опецарионной системы.

В Windows и Linux разные переносы строк, поэтому когда Python применяет свой универсальный перенос строки количество байт может увеличится на 1 а может остаться прежним.

write() возвращает количество байт, переданных в файл, а не фактический размер записанных данных.

Дописать в файл

Если нужно добавить новые данные к предыдущему содержимому без удаления - используется опция a (append)

with open('log.txt', 'a') as f: f.write("some text")

Рассмотрим пример добавления данных в файл sites.md с помощью метода writelines()

www.topbicycle.ru www.heihei.ru www.eth1.ru

>>> f = open('sites.md', mode='at', encoding='utf-8') >>> f.writelines(['www.aviasales.ru\n', 'www.booking.com', 'www.tutu.ru\n', 'www.velodrive.ru', 'www.velosklad.ru']) >>> f.close() >>> exit()

cat sites.md

www.topbicycle.ru www.heihei.ru eth1.ruwww.aviasales.ru www.booking.comwww.tutu.ru www.velodrive.ruwww.velosklad.ru

Видно, что переносы строк появились только там, где из указали вручную.

Записать json в файл

import json # нужно где-то взять json r = urllib.request.urlopen('http://urn.su/api/v1/getjson') rr = r.read() rj = json.loads(rr) with open('file.txt', 'w') as f: json.dump(rj, f)

Удалить первые несколько строк файла

with open('log.txt', 'a') as fin: data = fin.readlines()[1:] with open('new.txt', 'w') as fout: fout.writelines(data)

Запись вывода программы в файл

Если вы запускаете скрипт из терминала, воспользуйтесь перенаправлением

python script.py > script.log

В самом скрипте можно временно подменить стандартный вывод.

Допустим я делаю запрос к API

(Подробнее про REST API)

import sys … # Сохраним ссылку на оригинальный stdout original_stdout = sys.stdout with open("log.txt", "a") as f: sys.stdout = f print(resp.data) sys.stdout = original_stdout

Тоже самое, если приходит json и хочется записать его красиво

import sys import json … with open("log.txt", "a") as f: sys.stdout = f print(json.dumps(resp.data, indent=4)) sys.stdout = original_stdout

Последовательность Рекамана

import sys from itertools import count, islice def sequence(): """Generate Recaman's sequence.""" seen = set() a = 0 for n in count(1): yield a seen.add(a) c = a - n if c < 0 or c in seen: c = a + n a = c def write_sequence(filename, num): """Write Recaman's sequence to a text file.""" f = open(filename, mode='wt', encoding='utf-8') f.writelines( f"{r}\n" for r in islice(sequence(), num + 1)) f.close() if __name__ == '__main__': write_sequence(filename=sys.argv[1], num=int(sys.argv[2]))

python recaman.py recaman.dat 1000
cat recaman.dat

1 3 6 2 7 … 2687 3685 2686 3686

"""Read and print an integer series.""" import sys def read_series(filename): f = open(filename, mode='rt', encoding='utf-8') series = [] for line in f: a = int(line.strip()) series.append(a) f.close() return series def main(filename): series = read_series(filename) print(series) if __name__ == "__main__": filename = sys.argv[1] main(filename)

python series.py recaman.dat

[0, 1, 3, 6, 2, 7 … 3684, 2687, 3685, 2686, 3686]

Если в файле будут неподходящие данные ошибка будет показана в Traceback, а до закрытия файла дело не дойдёт.

echo badidea >> recaman.dat
python series.py recaman.dat

Traceback (most recent call last): File "/home/andrei/series.py", line 21, in <module> main(filename) File "/home/andrei/series.py", line 15, in main series = read_series(filename) File "/home/andrei/series.py", line 8, in read_series a = int(line.strip()) ValueError: invalid literal for int() with base 10: 'badidea'

Это серьёзная проблема, и обычно решается использованием менеджера контекста , но можно решить и добавлением try , finally

Модифицируем функцию read_series() заодно добавив генератор списка

def read_series(filename): try: f = open(filename, mode='rt', encoding='utf-8') return [int(line.strip()) for line in f] finally: f.close()

С with получается короче.

def read_series(filename): with open(filename, mode='rt', encoding='utf-8') as f: return [int(line.strip()) for line in f]

В write_sequence() из recaman.py тоже лучше применить with

def write_sequence(filename, num): """Write Recaman's sequence to a text file.""" with open(filename, mode='wt', encoding='utf-8') as f: f.writelines( f"{r}\n" for r in islice(sequence(), num + 1))

Пример работы с bytes

bmp.py

"""A module for dealing with BMP bitmap image files.""" def write_grayscale(filename, pixels): """Creates and writes a grayscale BMP file. Args: filename: The name of the BMP file to be created. pixels: A rectangular image stored as a sequence of rows. Each row must be an iterable series of integers in the range 0-255. Raises: ValueError: If any of the integer values are out of range. OSError: If the file couldn't be written. """ height = len(pixels) width = len(pixels[0]) with open(filename, 'wb') as bmp: # BMP Header bmp.write(b'BM') size_bookmark = bmp.tell() # The next four bytes hold the filesize as a 32-bit bmp.write(b'\x00\x00\x00\x00') # little-endian integer. Zero placeholder for now. bmp.write(b'\x00\x00') # Unused 16-bit integet - should be zero bmp.write(b'\x00\x00') # Unused 16-bit integet - should be zero pixel_offset_bookmark = bmp.tell() # The next four bytes hold the integer offset to the bmp.write(b'\x00\x00\x00\x00') # pixel data. Zero placeholder for now. # Image Header bmp.write(b'\x28\x00\x00\x00') # Image header size in bytes - 40 decimal bmp.write(_int32_to_bytes(width)) # Image width in pixels bmp.write(_int32_to_bytes(height)) # Image height in pixels bmp.write(b'\x01\x00') # Number of image planes bmp.write(b'\x08\x00') # Bits per pixel 8 for grayscale bmp.write(b'\x00\x00\x00\x00') # No compression bmp.write(b'\x00\x00\x00\x00') # Zero for uncompressed images bmp.write(b'\x00\x00\x00\x00') # Unused pixels per meter bmp.write(b'\x00\x00\x00\x00') # Unused pixels per meter bmp.write(b'\x00\x00\x00\x00') # Use whole color table bmp.write(b'\x00\x00\x00\x00') # All colors are important # Color palette - a linear grayscale for c in range(256): bmp.write(bytes((c, c, c, 0))) # Blue, Green, Red, Zero # Pixel data pixel_data_bookmark = bmp.tell() for row in reversed(pixels): # BMP files are bottom to top row_data = bytes(row) bmp.write(row_data) padding = b'\x00' * ((4 - (len(row) % 4)) % 4) # Pad row to multiple of four bytes bmp.write(padding) # End of file eof_bookmark = bmp.tell() # Fill in file size placeholder bmp.seek(size_bookmark) bmp.write(_int32_to_bytes(eof_bookmark)) # Fill in pixel offset placeholder bmp.seek(pixel_offset_bookmark) bmp.write(_int32_to_bytes(pixel_data_bookmark)) def _int32_to_bytes(i): """Convert an integer to four bytes in little-endian format.""" # &: Bitwise-and # >>: Right-shift return bytes( (i & 0xff, i >> 8 & 0xff, i >> 16 & 0xff, i >> 24 & 0xff) )

fractal.py

import math def mandel(real, imag): """The logarighm of number of iterations needed to determine whether a complex point is in the Mandelbrot set. Args: real: The real coordinate imag: The imaginary coordinate Returns: An integer in the range 1-255. """ x = 0 y = 0 for i in range(1, 257): if x*x + y*y > 4.0: break xt = real + x*x - y*y y = imag + 2.0 * x * y x = xt return int(math.log(i) * 256 / math.log(256)) -1 def mandelbrot(size_x, size_y): """Make an Mandelbrot set image. Args: size_x: Image width size_y: Image height Returns: A list of lists of integers in the range 0-255 """ return [[mandel((3.5 * x / size_x) - 2.5, (2.0 * y / size_y) - 1.0) for x in range(size_x)] for y in range(size_y)]

>>> import fractal >>> pixels = fractal.mandelbrot(448, 256) >>> import reprlib >>> reprlib.repr(pixels)

'[[31, 31, 31, 31, 31, 31, ...], [31, 31, 31, 31, 31, 31, ...], [31, 31, 31, 31, 31, 31, ...], [31, 31, 31, 31, 31, 31, ...], [31, 31, 31, 31, 31, 31, ...], [31, 31, 31, 31, 31, 31, ...], ...]'

>>> import bmp >>> bmp.write_grayscale("mandel.bmp", pixels)

Определение размеров bmp изображения

def dimensions(filename): """Determine the dimensions in pixels of a BMP image. Args: filename: The filename of a BMP file. Returns: A tuple containing two integers with the width and height in pixels. Raises: ValueError: If the file was not a BMP file. OSError: If there was a problem reading the file. """ with open(filename, 'rb') as f: magic = f.read(2) if magic != b'BM': raise ValueError(f"{filename} is not a BMP file") f.seek(18) width_bytes = f.read(4) height_bytes = f.read(4) return ( _bytes_to_int32(width_bytes), _bytes_to_int32(height_bytes)) def _bytes_to_int32(b): "Convert a bytes object containing four bytes into an integer." return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)

>>> import bmp >>> bmp.dimensions("mandel.bmp")

(448, 256)

encode()

>>> s = "abcd" >>> len(s) 4 >>> s.encode() b'abcd' >>> s = "сайт" >>> len(s) 4 >>> s.encode() b'\xd1\x81\xd0\xb0\xd0\xb9\xd1\x82' >>> s = "сайт" >>> s.encode('iso-8859-5') b'\xe1\xd0\xd9\xe2' >>> s.encode('utf-8') b'\xd1\x81\xd0\xb0\xd0\xb9\xd1\x82'

Японские символы

>>> j = "平仮" >>> len(j) 2 >>> j.encode() b'\xe5\xb9\xb3\xe4\xbb\xae' >>> j.encode("SHIFT-JIS") b'\x95\xbd\x89\xbc'

Преобразовать в "кириллический" iso-8859-5 не получится

>>> j.encode('iso-8859-5') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.8/encodings/iso8859_5.py", line 12, in encode return codecs.charmap_encode(input,errors,encoding_table) UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-1: character maps to <undefined>

j.encode('iso-8859-5') # это тоже что и j.encode('iso-8859-5', errors='strict')

>>> j.encode('iso-8859-5', errors='ignore') b'' >>> j = "Japanese symbol 平仮" >>> j.encode('iso-8859-5', errors='ignore') b'Japanese symbol '

Определить кодировки файлов

Пример скрипта для определения кодировок файлов. О том как создать файлы в разных кодировках в Linux читайте здесь

python -m pip install python-magic

import magic def get_encoding(sample): blob = open(sample, 'rb').read() m = magic.open(magic.MAGIC_MIME_ENCODING) m.load() encoding = m.buffer(blob) return encoding files = ['utf-8-file', 'windows-1251-file', 'shift-jis-file'] for f in files: print(get_encoding(f))

utf-8 iso-8859-1 unknown-8bit

С определением SHIFT-JIS пока проблемы

Путь до файла

python -m pip install pathlib

import pathlib from pathlib import Path dir_path = pathlib.Path.cwd() print(dir_path)

/home/andrei/sandbox/python/file_path

Прочитать файл из другой директории

Предположим, что мы находимся в директории one проекта file_path:

file_path/ ├── one │ └── path.py └── two └── sites.txt

cat ../two/sites.txt

www.heihei.ru

Прочитать файл sites.txt с помощью Python поможет библиотека pathlib

import pathlib from pathlib import Path dir_path = pathlib.Path.cwd() path = Path(dir_path, "..", "two", "sites.txt") with open(path, "r") as f: sites = f.read() print(sites)

python path.py

www.heihei.ru

glob

Оффициальная документация

Предположим, что мы находимся в директории glob_ex:

glob_ex/ └── sample_files ├── it.txt └── travel.txt

# glob_ex.py import glob files = glob.glob("./sample_files/*.txt") print(files)

python glob_ex.py

['./sample_files/it.txt', './sample_files/travel.txt']

# glob_ex.py import glob files = glob.glob("./sample_files/*.txt") for file in files: print(file) with open(file, "r") as f: lines = f.readlines() for line in lines: print(line)

python glob_ex.py

./sample_files/it.txt beget.com kaspersky.com ./sample_files/travel.txt aviasales.com booking.com

Похожие статьи
Python
Интерактивный режим
str: строки
\: перенос строки
Списки []
if, elif, else
Циклы
Функции
try except
Пакеты
*args **kwargs
ООП
enum
Опеределить тип переменной Python
Тестирование с помощью Python
Работа с REST API на Python
Скачать файл по сети
SQLite3: работа с БД
datetime: Дата и время в Python
json.dumps
Selenium + Python
Сложности при работе с Python
DJANGO
Flask
Скрипт для ZPL принтера
socket :Python Sockets
Виртуальное окружение
subprocess: выполнение bash команд из Python
multiprocessing: несколько процессов одновременно
psutil: cистемные ресурсы
sys.argv: аргументы командной строки
PyCharm: IDE
pydantic: валидация данных
paramiko: SSH из Python
enumerate
logging: запись в лог
Обучение программированию на Python