Поиск различий между деревьями

poisk razlichij mezhdu derevyami Законченные системные программы

Мы только что реализовали инструмент, отбирающий уникальные имена файлов и каталогов. Теперь нам осталось реализовать инструмент обхода дерева, который будет применять функции из модуля dirdiff на каждом уровне, чтобы отобрать уникальные файлы и каталоги; явно сравнит содержимое общих файлов и обойдет общие каталоги. Эти операции осуществляет сценарий из примера 6.12.

Пример 6.12. PP4E\System\Filetools\diffall.py

############################################################################## Порядок использования: python diffall.py dir1 dir2”.

Выполняет рекурсивное сравнение каталогов: сообщает об уникальных файлах, существующих только в одном из двух каталогов, dir1 или dir2; сообщает о файлах с одинаковыми именами и с разным содержимым, присутствующих в каталогах dir1 и dir2; сообщает об разнотипных элементах с одинаковыми именами, присутствующих в каталогах dir1 и dir2; то же самое выполняется для всех подкаталогов с одинаковыми именами, находящихся внутри деревьев каталогов dir1

и dir2. Сводная информация об обнаруженных отличиях помещается в конец вывода, однако в процессе поиска в вывод добавляется дополнительная информация об отличающихся и уникальных файлах с метками DIFFи unique”. Новое: (в 3 издании) для больших файлов введено ограничение на размер читаемых блоков в 1 Мбайт, (3 издание) обнаруживаются одинаковые имена файлов/каталогов, (4 издание) исключены лишние вызовы os.listdir() в dirdiff.comparedirs() за счет передачи результатов.

##############################################################################

import os, dirdiff

blocksize = 1024 * 1024 # не более 1 Мбайта на одну операцию чтения

def intersect(seq1, seq2):

Возвращает все элементы, присутствующие одновременно в seq1 и seq2;

выражение set(seq1) & set(seq2) возвращает тот же результат, но множества являются неупорядоченными коллекциями, поэтому при их использовании может быть утерян порядок следования элементов, если он имеет значение для некоторых платформ

return [item for item in seq1 if item in seq2]

def comparetrees(dir1, dir2, diffs, verbose=False):

Сравнивает все подкаталоги и файлы в двух деревьях каталогов;

для предотвращения кодирования/декодирования содержимого и преобразования символов конца строки использует двоичный режим доступа к файлам, так как деревья могут содержать произвольные двоичные и текстовые файлы; функции listdir может потребоваться передавать аргумент типа bytes, если могут встречаться имена файлов, недекодируемые на других платформах

#  сравнить списки с именами файлов

print(‘-’ * 20)

names1 = os.listdir(dir1)

names2 = os.listdir(dir2)

if not dirdiff.comparedirs(dir1, dir2, names1, names2):

diffs.append(‘unique files at %s %s’ % (dir1, dir2))

print(‘Comparing contents’)

common = intersect(names1, names2)

missed = common[:]

#  сравнить содержимое файлов с одинаковыми именами for name in common:

path1 = os.path.join(dir1, name)

path2 = os.path.join(dir2, name)

if os.path.isfile(path1) and os.path.isfile(path2):

missed.remove(name)

file1 = open(path1, ‘rb’)

file2 = open(path2, ‘rb’)

while True:

bytes1 = file1.read(blocksize)

bytes2 = file2.read(blocksize) if (not bytes1) and (not bytes2): if verbose: print(name, ‘matches’) break

if bytes1 != bytes2:

diffs.append(‘files differ at %s %s’ % (path1, path2)) print(name, ‘DIFFERS’) break

#  рекурсивно сравнить каталоги с одинаковыми именами for name in common:

path1 = os.path.join(dir1, name)

path2 = os.path.join(dir2, name)

if os.path.isdir(path1) and os.path.isdir(path2):

missed.remove(name)

comparetrees(path1, path2, diffs, verbose)

# одинаковые имена, но оба не являются одновременно файлами или каталогами? for name in missed:

diffs.append(‘files missed at %s %s: %s’ % (dir1, dir2, name)) print(name, ‘DIFFERS’)

if __name__ == ‘__main__’:

dir1, dir2 = dirdiff.getargs()

diffs = []

comparetrees(dir1, dir2, diffs, True) # список diffs изменяется в print(‘=’ * 40) # процессе обхода, вывести diffs

if not diffs:

print(‘No diffs found.’)

else:

print(‘Diffs found:’, len(diffs)) for diff in diffs: print(‘-’, diff)

В каждом каталоге этого дерева данный сценарий просто использует модуль dirdiff, чтобы обнаружить уникальные имена, а затем сравнивает общие имена, присутствующие одновременно в обоих списках содержимого каталогов. Рекурсивный спуск в подкаталоги выполняется только после сравнения всех файлов на каждом уровне, чтобы вывод сценария было удобнее воспринимать на глаз (трассировка обхода подкаталогов выводится ниже результатов сравнения файлов — они не смешиваются).

Обратите внимание на список misses, добавленный в сценарий в третьем издании книги. Очень маловероятно, но не невозможно, чтобы одно и то же имя в одном каталоге соответствовало файлу, а в другом — подкаталогу. Кроме того, обратите внимание на переменную blocksize. Как и в сценарии копирования деревьев каталогов, который мы видели выше, вместо того чтобы слепо пытаться читать файлы в память целиком, мы установили ограничение в 1 Мбайт для каждой операции чтения — на тот случай, если какие-нибудь файлы окажутся слишком большими, чтобы их можно было загрузить в память. Предыдущая версия этого сценария, без этого ограничения, которая читала файлы целиком, как показано ниже, на некоторых компьютерах возбуждала исключение MemoryError:

bytes1 = open(path1, ‘rb’).read() bytes2 = open(path2, ‘rb’).read() if bytes1 == bytes2:

Этот код проще, но менее практичен в ситуациях, когда могут встречаться очень большие файлы, не умещающиеся в память целиком (например, файлы образов CD и DVD). В новой версии файл читается в цикле порциями не более 1 Мбайта, пока не будет возвращена пустая строка, свидетельствующая об окончании файла. Файлы считаются одинаковыми, если совпадают все прочитанные из них блоки и конец файла достигнут одновременно.

Помимо всего прочего, мы обрабатываем содержимое файлов в двоичном режиме, чтобы подавить операцию декодирования их содержимого и предотвратить преобразование символов конца строки, потому что деревья каталогов могут содержать произвольные двоичные и текстовые файлы. Держим также наготове обычное замечание о необходимости передачи аргумента типа bytes функции os.listdir на платформах, где имена файлов могут оказаться недекодируемыми (например, с помощью dir1.encode()). На некоторых платформах может также потребоваться определять и пропускать некоторые файлы специальных типов, чтобы обеспечить полную универсальность, но в моих каталогах такие файлы отсутствовали, поэтому я не включил эту проверку в сценарий.

В четвертом издании было внесено одно незначительное изменение: для каждого подкаталога результаты os.listdir теперь собираются и передаются только один раз, чтобы избежать лишних вызовов функций из модуля dirdiff, — не самая большая победа, но на моем медлительном нетбуке, который я использовал во время работы над этим изданием, каждый лишний цикл на счету.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий