Практические применения: работа с файлами и папками в ОС

С помощью короткой программы на питоне можно решить довольно широкий спектр задач которые до появления компьютеров просто не существовали. Это работа с файлами и папками операционной системы, работа с изображениями, mp3, pdf, электронной почтой, получение информации из сети интернет и много другое. Почти для всех задач, которые возникают, в сети можно найти подходящий рецепт на языке питон. На данный момент самым большим источником таких рецептов является сайт http://stackoverflow.com. Увы, он на английском, но у него есть и русская версия (в 1000 раз более бедная). Впрочем, обычно не нужно много английского, чтобы понять, что происходит.

Для несложных задач рецепты найдутся и на русском языке. Правда если на правильный английский запрос ответ уже будет в первой строке выдачи (и это будет сайт stackoverflow), то с русским оно будет несколько сложнее.

В этом листке мы будем пытаться научиться двум вещам: применять питон в практических задачах, и искать пути решения своих технических задач в интернете.

Если вы нашли рецепт, то следует убедиться, что рецепт на Python3, а не на Python2. Их легко отличить по print без скобок (такая конструкция в Python3 не работает). Зачастую print — это единственное, что нужно поправить. Но это, увы, не всегда так.

В каждой задаче нужно написать программу, которая решает поставленную проблему. В тестовую систему необходимо сдать текст программы, строчку-разделитель ####################, а затем ответ на задачу. Например, если задача — «Вычислить 2 в сотой степени», то возможный правильный ответ такой:

PS. Ах, да. 10 лет назад задачи этого листка было принято решать при помощи модуля os. А последние 3 года — при помощи библиотеки pathlib. Лучше выбирайте свежие «рецепты» с использованием pathlib.

print(2 ** 100)
####################
1267650600228229401496703205376

A: Список файлов в папках

В архиве вы найдёте структуру папок с таксономией приматов (виды, семейства и т.д.). Где-то в глубине там даже есть человек разумный :). Необходимо вывести список из всех файлов, которые есть в папке Primates или её подпапках. Порядок файлов не важен.

Папку Primates из архива нужно положить куда-нибудь и скопировать путь к ней. Если путь имеет вид C:\Some\Directory\Users\Foo\Primates, то в начале программы рекомендуется сделать

os.chdir(r'C:\Some\Directory\Users\Foo')
После этого папка Primates окажется прямо в текущей рабочей папке программы.

Если бы стартовой была папка Primates\Haplorrhini\Simiiformes\Catarrhini\Hominoidea\Hominidae, то ответ был бы такой:

import os
...
####################
Gorilla_beringei_beringei.txt
Gorilla_beringei_graueri.txt
Gorilla_gorilla_diehli.txt
Gorilla_gorilla_gorilla.txt
Gorilla_gorilla_uellensis.txt
Homo_sapiens.txt
Pan_paniscus.txt
Pan_troglodytes_ellioti.txt
Pan_troglodytes_schweinfurthii.txt
Pan_troglodytes_troglodytes.txt
Pan_troglodytes_vellerosus.txt
Pan_troglodytes_verus.txt
Pongo_abelii.txt
Pongo_pygmaeus_pygmaeus.txt

B: Список файлов в папках с полными путями

В условиях задачи A выведите не только имена файлов, но и полные пути до них от начальной папки Primates.

Если бы стартовой была папка Primates\Haplorrhini\Simiiformes\Catarrhini\Hominoidea\Hominidae, то ответ был бы такой:

import os
...
####################
Hominidae\Homininae\Gorilla\Gorilla_beringei\Gorilla_beringei_beringei.txt
Hominidae\Homininae\Gorilla\Gorilla_beringei\Gorilla_beringei_graueri.txt
Hominidae\Homininae\Gorilla\Gorilla_gorilla\Gorilla_gorilla_diehli.txt
Hominidae\Homininae\Gorilla\Gorilla_gorilla\Gorilla_gorilla_gorilla.txt
Hominidae\Homininae\Gorilla\Gorilla_gorilla\Gorilla_gorilla_uellensis.txt
Hominidae\Homininae\Homo\Homo_sapiens.txt
Hominidae\Homininae\Pan\Pan_paniscus.txt
Hominidae\Homininae\Pan\Pan_troglodytes_ellioti.txt
Hominidae\Homininae\Pan\Pan_troglodytes_schweinfurthii.txt
Hominidae\Homininae\Pan\Pan_troglodytes_troglodytes.txt
Hominidae\Homininae\Pan\Pan_troglodytes_vellerosus.txt
Hominidae\Homininae\Pan\Pan_troglodytes_verus.txt
Hominidae\Ponginae\Pongo\Pongo_abelii.txt
Hominidae\Ponginae\Pongo\Pongo_pygmaeus\Pongo_pygmaeus_pygmaeus.txt

C: Прочитать все файлы в папке

В каждом файле из задачи A записан идентификатор этого таксона в открытом дереве жизни (Open Tree of Life). Выведите список этих идентификаторов.

Если бы стартовой была папка Primates\Haplorrhini\Simiiformes\Catarrhini\Hominoidea\Hominidae, то ответ был бы такой:

import os
...
####################
ott624503
ott317556
ott835035
ott417953
ott620805
ott770315
ott158484
ott112831
ott752845
ott767875
ott84217
ott752847
ott770295
ott770308

D: Список всех подпапок

В условиях задачи A выведите список имён всех подпапок в папке Primates или её подпапках.

Если бы стартовой была папка Primates\Haplorrhini\Simiiformes\Catarrhini\Hominoidea\Hominidae, то ответ был бы такой:

import os
...
####################
Homininae
Ponginae
Gorilla
Homo
Pan
Gorilla_beringei
Gorilla_gorilla
Pongo
Pongo_pygmaeus

E: Список папок с полными путями

В условиях задачи 04 выведите полные пути к папкам от Primates.

Если бы стартовой была папка Primates\Haplorrhini\Simiiformes\Catarrhini\Hominoidea\Hominidae, то ответ был бы такой:

import os
...
####################
Hominidae\Homininae
Hominidae\Ponginae
Hominidae\Homininae\Gorilla
Hominidae\Homininae\Homo
Hominidae\Homininae\Pan
Hominidae\Homininae\Gorilla\Gorilla_beringei
Hominidae\Homininae\Gorilla\Gorilla_gorilla
Hominidae\Ponginae\Pongo
Hominidae\Ponginae\Pongo\Pongo_pygmaeus

Практическое задание: восстановление фотографий

В новой серии задач мы отрабатываем следующий сценарий: из-за резкого скачка электричества ваш компьютер вырубился во время записи файла на диск. В результате повреждена файловая система, поэтому уже невозможно найти свои файлы в "обычных" местах. На диске остались фотографии, которые вам дороги. С помощью специальной программы, которая сканирует весь диск и ищет последовательности байтов, похожие на фотографии, вы добыли всё, что можно. Так как файловая система умерла, то имён файлов и прочих атрибутов нет, есть только гора файлов с бессмысленными именами. Так как вы много работали с фотографиями, то там много повторов фотографий. А вот и архив с этими фотографиями. Для его открытия используйте 7z (брать отсюда).

План по наведению порядка:

Для того, чтобы каждый раз не клеить полный путь к имени файла, удобно перейти прямо в директорию к фотографиям при помощи
os.chdir('C:\\some\\path\\resc_photos')
Итак, поехали!

F: md5-хеши файлов

Для каждого файла из архива посчитайте его md5-хеш. Выведите все пары (ИМЯ_ФАЙЛА, ХЕШ). Порядок файлов неважен.

Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
import hashlib
...
####################
0spuuzog.jpg 2b04cbfba96acc2b20fb41a65c00116d
1e1ztfom.jpg a46e61e2cf9725d569ad755dbdd189c0
82t56870.jpg 2b04cbfba96acc2b20fb41a65c00116d

G: Отбор уникальных файлов

Отберите все уникальные фотографии и выведите их имена.

Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
import hashlib
...
####################
0spuuzog.jpg
1e1ztfom.jpg

H: Удаление лишнего

Получите список всех файлов, которые не лежат в списке из задачи G. Удалите все эти повторяющиеся файлы.

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит список удаляемых файлов. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
import hashlib
...
####################
82t56870.jpg

I: Дата съёмки

Известно, что фотографии сняты в августе 2015 года. В начале каждой фотографии находится EXIF — метаданные, например, дата-время съёмки, модель камеры и т.д. Теоретически их можно расшифровать, но сейчас в этом нет необходимости. Нужную дату можно найти и так. Откройте файл при помощи notepad++, и вы её увидите. После этого будет не сложно добыть дату для каждой фотографии автоматически. Здесь следует учитывать, что данные в файле не текстовые, а бинарные, поэтому открывать файл стоит так:

open(filename, 'rb')
Здесь r отвечает за чтение, а b — за то, что чтение бинарных данных. Бинарные строки очень похожи на обычные, только в коде перед ними стоит буква r. С ними можно делать почти все операции, что и с обычными текстовыми. Те символы бинарной строки, код которых не лежит в диапазоне acsii (от 32 до 127), выводятся как их код в 16-ричной системе счисления. Для получения из обычной текстовой строки бинарной используется метод encode:
>>> print('Привет, world!'.encode('utf-8'))
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, world!'
Для обратного преобразования — метод decode:
>>> print(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, world!'.decode('utf-8'))
Привет, world!

Выведите для каждого из оставшихся файлов дату и время съёмки в том виде, в котором они добываются из файла. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
...
####################
0spuuzog.jpg b'2015:08:23 14:26:12'
1e1ztfom.jpg b'2015:08:19 16:05:07'

J: Переименование

Теперь ничего не стоит переименовать файлы в имена вида "yyyy-mm-dd_hh-mm-ss.jpg". Ну, почти ничего, кроме того, что некоторые фотографии сняты в одну и ту же секунду. Если вдруг так окажется, то добавьте в конец имени файла перед ".jpg" один, два или три символа "_" (например, "2015-08-23_14-26-12_.jpg").

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит имена новых файлов. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
...
####################
2015-08-23_14-26-12.jpg
2015-08-19_16-05-07.jpg

K: Чтение EXIF

Решение в предыдущих двух задачах не сработает, если у нас будет архив фотографий за 10 лет. Мы не сможем уверенно искать дату съёмки по строке "2015.08", ведь это может оказаться датой изменения, а не съёмки. Поэтому более правильное решение — расшифровать EXIF. Стандарт открыт, поэтому можно написать свою программу по его расшифровке. Однако это достаточно трудоёмко. Один из плюсов питона в том, что большинство подобных задач уже решены, и всё, что требуется, это найти подходящий пакет. В данном случае один из подходящих нам пакетов — это exifread.

Установка пакета exifread
Установка пакетов для Python в операционной системе Windows, увы, зачастую вызывает затруднения. Чтобы его установить, нужно выполнить в командной строке pip install exifread (win+R, затем cmd). Если у вас старая версия Python, не установлен pip, или при установке вы не поставили галочку "Добавить Python в PATH", то это не сработает. Тогда попробуйте запустить такую программу:
import os, sys python = sys.executable user = '--user' if 'venv' not in python else '' cmd = '"{}" -m pip install exifread --upgrade {}'.format(python, user) print(cmd) os.system(cmd)

Напишите программу, которая получает дату и время съёмки из EXIF для каждой фотографии при помощи модуля exifread. В тестовую систему необходимо сдать код программы, которая делает это, а также выводит полученные временные отметки. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
import exifread
...
####################
2015:08:23 14:26:12
2015:08:19 16:05:07

L: Выбор объектива

При выборе объектива для фотоаппарата возникает вопрос: какие диапазон фокусных расстояний (углов поля зрения, см. картинку в википедии) вам необходим. Что выбирать, длиннофокусный объектив, широкоугольный объектив и т.д.? Довольно надёжный способ — посмотреть, какими фокусными расстояними вы пользуетесь.

Напишите программу, которая получает фокусное расстояние кадра из EXIF для каждой фотографии при помощи модуля exifread. Для каждого используемого фокусного расстояния выведите количество кадров (нужно отсортировать по увеличению фокусного расстояния). Постройте график количества кадров в зависимости от фокусного расстояния (нужно показать на экране). Учтите, что вам нужно эквивалентное фокусное расстояние, так как именно оно участвует в маркировке объективов.

В тестовую систему необходимо сдать код программы, которая делает это, а также выводит количества кадров. Если бы в папке были бы только файлы 0spuuzog.jpg, 1e1ztfom.jpg и 82t56870.jpg, то правильный ответ был бы таким:

import os
import exifread
...
####################
24: 1
70: 1

M: Разложить по полочкам

После того, как мы «восстановили» имена файлов с фотографиями, появилась необходимость разложить их в отдельные папки в соответствии с поездками.

Будем тренироваться на «кошках». Внутри архива вы найдёте несколько файлов вида 2013-02-04_04-46-01.txt. Нужно разложить их по папкам вида yyyy/mm/dd, где yyyy, mm и dd — соответствующие части даты, а также переименовать: убрать из имени файла дату.

В тестовую систему необходимо сдать zip-архив, в котором находится папка crt_dirs с нужно структурой, а также py-файл с программой. Если бы архив из условия состоял лишь из файлов 2013-01-16_00-24-33.txt, 2013-01-16_16-40-26.txt и 2015-11-25_16-51-47.txt, то в правильная структура в zip файле ответа выглядела бы так:

crt_dirs/2013/01/16/00-24-33.txt
crt_dirs/2013/01/16/16-40-26.txt
crt_dirs/2015/11/25/16-40-26.txt
crt_dirs/my_pgm.py