Продвинутая работа с файлами в Python

Содержание
Введение
seek(0): перемещение в начало файла
Записать json в файл
Удалить первые несколько строк файла
Запись вывода программы в файл
Определить кодировки файлов
Прочитать файл из другой директории
Найти строку
Удалить определённую строку
Удалить все между тегами
Дописать после строки
Дописать после тега
Удалить расширение файла
Похожие статьи

Введение

В этой статье вы можете познакомиться с дополнительными примерами работы с файлами в Python 3.

Подразумевается, что вы уже владеете материалом из предыдущей статьи Основы работы с файлами в Python

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

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

python3 files.py

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

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

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

topbicycle.ruheihei.rueth1.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'

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

import json # нужно где-то взять json r = urllib.request.urlopen('http://eth1.ru/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

Пример работы с 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)

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

Пример скрипта для определения кодировок файлов. О том как создать файлы в разных кодировках в 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 пока проблемы

Путь до файла

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

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

Найти строку

Рассмотрим файл sites.md и постараемся найти строку heihei.ru

topbicycle.ru heihei.ru eht1.ru

Воспользуемся тем, что менеджер контекста возвращает итератор и пройдёмся по файлу методом next()

with open("sites.md", "r") as f: while True: try: line = str(next(f)) print(line) except StopIteration: raise ValueError("End of file") if line == "heihei.ru\n": print("Found heihei.ru!")

topbicycle.ru heihei.ru Found heihei.ru! eth1.ru Traceback (most recent call last): File "/mnt/c/Users/Andrei/readline_ex.py", line 4, in <module> line = str(next(f)) ^^^^^^^ StopIteration During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/mnt/c/Users/Andrei/readline_ex.py", line 7, in <module> raise ValueError("End of file") ValueError: End of file

С помощью цикла for

with open("sites.md", "r") as f: for line in f: if line == "heihei.ru\n": print("Found heihei.ru") else: print("Some other line found")

Some other line found Found heihei.ru Some other line found

Удалить определённую строку

Рассмотрим файл sites.md и постараемся удалить все строки heihei.ru

topbicycle.ru heihei.ru eht1.ru

with open("sites.md", "r") as f: with open("new_file.txt", "w") as nf: for line in f: if line != "heihei.ru\n": nf.write(line) with open("new_file.txt", "r") as nf: print(nf.readlines())

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

РЕКЛАМА от Яндекса. Может быть недоступна в вашем регионе

Конец рекламы. Если там пусто считайте это рекламой моей телеги

Удалить все между двумя строками

Рассмотрим файл sites.md

topbicycle.ru heihei.ru <bikes> Forward Stark Stels </bikes> eth1.ru devhops.ru

Удалим всё между тегами <bikes> и </bikes> и запишем в новый файл.

Включая сами теги:

copy = True with open("sites.md", "r") as f: print(f.readlines()) with open("sites.md", "r") as f: with open("clean.md", "w") as nf: for line in f: if line == "<bikes>\n": copy = False if copy: nf.write(line) if line == "</bikes>\n": copy = True with open("clean.md", "r") as nf: print(nf.readlines())

['topbicycle.ru\n', 'heihei.ru\n', '<bikes>\n', 'Forward\n', 'Stark\n', 'Stels\n', '</bikes>\n', 'eth1.ru\n', 'devhops.ru\n'] ['topbicycle.ru\n', 'heihei.ru\n', 'eth1.ru\n', 'devhops.ru\n']

Оставляя теги:

copy = True with open("sites.md", "r") as f: print(f.readlines()) with open("sites.md", "r") as f: with open("clean.md", "w") as nf: for line in f: if line == "</bikes>\n": copy = True if copy: nf.write(line) if line == "<bikes>\n": copy = False with open("clean.md", "r") as nf: print(nf.readlines())

['topbicycle.ru\n', 'heihei.ru\n', '<bikes>\n', 'Forward\n', 'Stark\n', 'Stels\n', '</bikes>\n', 'eth1.ru\n', 'devhops.ru\n'] ['topbicycle.ru\n', 'heihei.ru\n', '<bikes>\n', '</bikes>\n', 'eth1.ru\n', 'devhops.ru\n']

Более универсальная версия с использованием функций

Здесь изменения будут записаны сразу в исходный файл.

def main(): path = "sites.md" tag = "<bikes>" lines = readlines(path) delete_between_tags(path, lines, tag) def readlines(path): with open(path, "r") as f: lines = f.readlines() return lines def delete_between_tags(path, lines, tag): # либо в цикле обрезать line.strip("\n") tag = str(tag) + "\n" taglist = list(tag) taglist.insert(1, "/") endtag = "".join(taglist) with open(path, "w") as f: copy = True for line in lines: if line == tag: copy = False if copy: f.write(line) if line == endtag: copy = True if __name__ == "__main__": main()

Тег не обязан быть в начале строки, поэтому скрипт можно доработать.

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

heihei.ru <topbicycle.ru> <bikes> Forward Stark Stels </bikes> </topbicycle.ru> eth1.ru devhops.ru

Если мы уверены в том, что каждый тег написан на отдельной строке, достаточно заменить line = tag на tag in line и тоже для endtag.

for line in lines: if tag in line: copy = False if copy: f.write(line) if endtag in line: copy = True

Строка

tag = str(tag) + "\n"

тоже становится не нужна.

heihei.ru <topbicycle.ru> </topbicycle.ru> eth1.ru devhops.ru

Дописать после строки

Разберём типичную задачу при работе с файлами, особенно с конфигурациоными - дописать что-то после нужной строки.

Рассмотим файл sites.ini

[DevHops.ru] ;Port = 0 ;Partner = Beget.com [TestSetup.ru] ;Port = 0 ;Partner = Авиасейлз [TopBicycle.ru] ;Port = 0 ;Partner = Велодрайв

Откроем сайту TestSetup.ru порт 5000

def read_lines(path, encoding="cp1252"): with open(file=path, mode="r", encoding=encoding) as f: lines = f.readlines() return lines def open_port(file: str, site: str, port: int, encoding="cp1252"): port_opened = False lines = read_lines(file, encoding=encoding) for line in lines: if line == f"[{site}]\n": server_setting_index = lines.index(line) + 1 lines.insert(server_setting_index, f" Port = {port}\n") port_opened = True break with open(file, "w+", encoding=encoding) as f: f.writelines(lines) if port_opened: print(f"INFO: Port {port} opened for {site}") else: print(f"WARN: Port {port} is not opened for {site}") if __name__ == "__main__": open_port("sites.ini", "TestSetup.ru", 5000)

[DevHops.ru] ;Port = 0 ;Partner = Beget.com [TestSetup.ru] Port = 5000 ;Port = 0 ;Partner = Aviasales.ru [TopBicycle.ru] ;Port = 0 ;Partner = Velodrive.ru

Дописать после тега

Рассмотрим файл sites.md

heihei.ru <topbicycle.ru> <bikes> Forward Stark Stels </bikes> </topbicycle.ru> eth1.ru devhops.ru

Допишем следущий текст после тега <bikes>

<german> Cube Drossiger Ghost </german>

def main(): path = "sites.md" tag = "<bikes>" text = """ <german> Cube Drossiger Ghost </german> """ lines = readlines(path) append_after_tag(path, lines, tag, text) def readlines(path): with open(path, "r") as f: lines = f.readlines() return lines def append_after_tag(path, lines, tag, text): tag = str(tag) + "\n" taglist = list(tag) taglist.insert(1, "/") endtag = "".join(taglist) with open(path, "w") as f: write_flag = True for line in lines: f.write(line) if tag in line: if write_flag == True: f.write(text) write_flag = False if __name__ == "__main__": main()

heihei.ru <topbicycle.ru> <bikes> <german> Cube Drossiger Ghost </german> Forward Stark Stels </bikes> </topbicycle.ru> eth1.ru devhops.ru

Удалить расширение файла

Рассмотрим скрипт remove_ex.py и файл openapi.yaml в той же директории. Сперва рассмотрим использование os.path.splitext()

demo `-- example |-- openapi.yaml `-- remove_ex.py

import os path = "openapi.yaml" print(os.path.splitext(path)[0])

Будем запускать скрипт из текущей директории и из родительской

python remove_ex.py

python example/remove_ex.py

openapi

Теперь предположим, что файл нужно в конце скрипта скопировать с помощью shutil

import os import shutil path = "openapi.yaml" print(os.path.splitext(path)[0]) shutil.copyfile(path, "backup.yaml")

python remove_ex.py

openapi

В текущей директории появится файл backup.yaml

demo/ `-- example |-- backup.yaml |-- openapi.yaml `-- remove_ex.py

python example/remove_ex.py

FileNotFoundError: [Errno 2] No such file or directory: 'openapi.yaml'

При запуске из текущей директории всё было хорошо, но из родительской shutil уже не находит исходный файл.

Используем полный путь до openapi.yaml

import os import shutil path = "openapi.yaml" path = os.path.join(os.path.dirname(__file__), path) print(os.path.splitext(path)[0]) shutil.copyfile(path, "backup.yaml")

Из текущей директории по-прежнему всё хорошо и backup.yaml создаётся в ней же.

python remove_ex.py

openapi

Тем не менее уже в этом случае при запуске из PyCharm результат будет

C:\Users\Andrei\demo\example\openapi openapi

При запуске из родительской директории

python example/remove_ex.py

example\openapi

backup.yaml создаётся в родительской директории

demo/ |-- backup.yaml `-- example |-- openapi.yaml `-- remove_ex.py

Выделить только имя файла поможет os.path.basename()

import os import shutil path = "openapi.yaml" path = os.path.join(os.path.dirname(__file__), path) print(os.path.splitext(os.path.basename(path))[0]) shutil.copyfile(path, "backup.yaml")

pathlib.Path().stem

Теперь рассмотрим использование pathlib.Path().stem

demo `-- example |-- openapi.yaml `-- remove_ex.py

from pathlib import Path path = "openapi.yaml" print(Path(path).stem)

Запускать будем из текущей директории и из родительской

python remove_ex.py

python example/remove_ex.py

openapi

Теперь предположим, что файл нужно в конце скрипта скопировать с помощью shutil

import shutil from pathlib import Path path = "openapi.yaml" print(Path(path).stem) shutil.copyfile(path, "backup.yaml")

python remove_ex.py

openapi

В текущей директории появится файл backup.yaml

demo/ `-- example |-- backup.yaml |-- openapi.yaml `-- remove_ex.py

python example/remove_ex.py

FileNotFoundError: [Errno 2] No such file or directory: 'openapi.yaml'

При запуске из текущей директории всё было хорошо, но из родительской shutil уже не находит исходный файл.

Используем полный путь до openapi.yaml

import shutil from pathlib import Path path = "openapi.yaml" path = os.path.join(os.path.dirname(__file__), path) print(Path(path).stem) shutil.copyfile(path, "backup.yaml")

Из текущей директории по-прежнему всё хорошо и backup.yaml создаётся в ней же.

python remove_ex.py

openapi

При запуске из родительской директории

python example/remove_ex.py

openapi

backup.yaml создаётся в родительской директории

demo/ |-- backup.yaml `-- example |-- openapi.yaml `-- remove_ex.py

Пример скрипта, который:

Конвертирет yaml файл в json

Сохраняте исходные версии в директорию backup

Указывает в названии бэкап файлов дату и время сохранения.

import yaml import json import time import shutil import os YAML_FILE_PATH = os.path.join(os.path.dirname(__file__), "openapi.yaml") JSON_FILE_PATH = os.path.join(os.path.dirname(__file__), "swagger.json") BACKUP_PATH = os.path.join(os.path.dirname(__file__), "backup") def yaml_to_json(yaml_file_path, json_file_path): with open(yaml_file_path, 'r') as yaml_file: yaml_data = yaml.safe_load(yaml_file) with open(json_file_path, 'w') as json_file: json.dump(yaml_data, json_file, indent=2) def backup(*args): for path in args: timestr = time.strftime("-%Y%m%d-%H%M%S") name = os.path.splitext(os.path.basename(path))[0] ext = os.path.splitext(os.path.basename(path))[1] name_with_time = name + timestr + ext dest = os.path.join(BACKUP_PATH, name_with_time) shutil.copyfile(path, dest) if __name__ == "__main__": if not os.path.exists(BACKUP_PATH): os.makedirs(BACKUP_PATH) backup(YAML_FILE_PATH, JSON_FILE_PATH) yaml_to_json(YAML_FILE_PATH, JSON_FILE_PATH)

Похожие статьи
Работа с файлами в Python
Python
Основы работы с файлами
glob: Работа с несколькими файлами
shutil: Работа с несколькими файлами
os
pathlib
Скачать файл по сети
.yaml.json
psutil: cистемные ресурсы
Обучение программированию на Python

РЕКЛАМА хостинга Beget, которым я пользуюсь более десяти лет

Изображение баннера

Конец рекламы хостинга Beget, который я всем рекомендую.

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: