Продвинутая работа с файлами в Python
Введение
В этой статье вы можете познакомиться с дополнительными примерами работы с файлами в Python 3.
Подразумевается, что вы уже владеете материалом из предыдущей статьи
Основы работы с файлами в Python
Создайте файл
files.py
и копируйте туда код из примеров.
Запустить файл можно командой
python3 files.py
seek(0): перемещение в начало файла
С помощью seek(0) можно поставить указатель в начало файла.
Перейдём в конец файла
sites.md
>>> 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
Найти строку
Рассмотрим файл 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)