Как и следовало ожидать, сценарий из предыдущего раздела отыскивает наименьший и наибольший файлы в дереве каталогов. Хотя поиск в полном дереве каталогов стандартной библиотеки Python дает более исчерпывающий ответ, тем не менее его никак нельзя признать полным: на компьютере могут находиться дополнительные модули, установленные в другие каталоги, включенные в путь поиска модулей, но за пределами дерева файлов с исходными текстами на языке Python. Чтобы дать более полный ответ, необходимо выполнить все тот же поиск в дереве каталогов, но при этом следует просмотреть все каталоги, включенные в путь поиска модулей. Это улучшение было добавлено в сценарий, представленный в примере 6.3, — он просматривает все модули Python, доступные для импортирования и располагающиеся непосредственно в пути поиска, а также расположенные во вложенных каталогах пакетов.
Пример 6.3. PP4E\System\Filetools\bigpy-path.py
Отыскивает наибольший файл с исходным программным кодом на языке Python, присутствующий в пути поиска модулей.
Пропускает каталоги, которые уже были просканированы; нормализует пути и регистр символов, обеспечивая корректность сравнения; включает в выводимые результаты счетчики строк. Здесь недостаточно использовать os.environ[‘PYTHONPATH’]: этот список является лишь подмножеством списка sys.path.
import sys, os, pprint
trace = 0 # 1=каталоги, 2=+файлы
visited = {}
allsizes = []
for srcdir in sys.path:
for (thisDir, subsHere, filesHere) in os.walk(srcdir):
if trace > 0: print(thisDir)
thisDir = os.path.normpath(thisDir)
fixcase = os.path.normcase(thisDir) if fixcase in visited:
continue
else:
visited[fixcase] = True
for filename in filesHere:
if filename.endswith(‘.py’):
if trace > 1: print(‘…’, filename)
pypath = os.path.join(thisDir, filename)
try:
pysize = os.path.getsize(pypath)
except os.error:
print(‘skipping’, pypath, sys.exc_info()[0]) else:
pylines = len(open(pypath, ‘rb’).readlines()) allsizes.append((pysize, pylines, pypath))
print(‘By size…’)
allsizes.sort()
pprint.pprint(allsizes[:3])
pprint.pprint(allsizes[-3:])
print(‘By lines…’)
allsizes.sort(key=lambda x: x[1])
pprint.pprint(allsizes[:3])
pprint.pprint(allsizes[-3:])
Этот сценарий выполняет обход всех каталогов, включенных в путь поиска, и для каждого из них пытается выполнить поиск в полном дереве подкаталогов. В результате получается тройной вложенный цикл — цикл по элементам пути, цикл по каталогам в дереве очередного элемента и цикл по файлам в каталоге. Так как путь поиска модулей может содержать каталоги, имена которых могут записываться произвольно, кроме всего прочего этот сценарий должен побеспокоиться, чтобы:
• Нормализовать пути к каталогам — исправить символы слеша и точки, чтобы привести имена каталогов к общепринятому виду.
• Нормализовать регистр символов в именах каталогов — привести к нижнему регистру все символы в именах файлов и каталогов в нечувствительном к регистру символов Windows, чтобы свести определение эквивалентности имен каталогов к простой операции сравнения строк, но не изменять регистр символов в Unix, где он имеет значение.
• Выявлять повторы, чтобы избежать повторного сканирования одних и тех же каталогов (один и тот же каталог может оказаться достижимым при сканировании разных элементов, включенных в sys.path).
• Пропускать все элементы, похожие на файлы, для которых функция os.path.getsize возбуждает исключение (по умолчанию os.walk сама молча игнорирует элементы на любом уровне вложенности, которые не являются каталогами).
• Избежать возможных ошибок декодирования символов Юникода в содержимом файлов, открывая их для подсчета строк в двоичном режиме. В текстовом режиме выполняется обязательное декодирование содержимого, а некоторые файлы в дереве каталогов библиотеки Python 3.1 не могут быть корректно декодированы в Windows. Перехват ошибок декодирования с помощью инструкции try позволил бы предотвратить преждевременное завершение программы, но при этом могли бы быть пропущены потенциальные кандидаты на звание большего или меньшего файла.
В эту версию был добавлен подсчет строк, что может существенно увеличить время работы сценария, но это очень интересный отчетный показатель. Фактически данная версия использует это значение как ключ сортировки, чтобы определить три наибольших и наименьших по количеству строк файла, — эти результаты могут не совпадать с результатами, когда наибольший и наименьший размер определяется по размеру файла в байтах. Ниже приводятся результаты выполнения этого сценария в Python 3.1 на моем компьютере, работающем под управлением Windows 7. Так как результаты зависят от платформы, наличия дополнительных расширений и настроек пути поиска, у вас могут получиться другие наибольший и наименьший файлы в пути sys.path:
C:\…\PP4E\System\Filetools> bigpy-path.py By size…
[(0, 0, ‘C:\\Python31\\lib\\build_class.py’),
(0, 0, ‘C:\\Python31\\lib\\email\\mime\\__init__.py’),
(0, 0, ‘C:\\Python31\\lib\\email\\test\\__init__.py’)] [(161613, 3754, ‘C:\\Python31\\lib\\tkinter\\__init__.py’), (211238, 5768, ‘C:\\Python31\\lib\\decimal.py’), (380582, 78, ‘C:\\Python31\\lib\\pydoc_data\\topics.py’)] By lines…
[(0, 0, ‘C:\\Python31\\lib\\build_class.py’),
(0, 0, ‘C:\\Python31\\lib\\email\\mime\\__init__.py’),
(0, 0, ‘C:\\Python31\\lib\\email\\test\\__init__.py’)]
[(147086, 4132, ‘C:\\Python31\\lib\\turtle.py’),
(150069, 4268, ‘C:\\Python31\\lib\\test\\test_descr.py’),
(211238, 5768, ‘C:\\Python31\\lib\\decimal.py’)]
И снова, если вам интересно увидеть, как выполняется обход каталогов, измените значение переменной trace. Как видите, результаты поиска наибольшего файла по размеру в байтах и по количеству строк отличаются. Это несоответствие, вероятно, мы должны тщательно обсудить на нашей следующей встрече.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011