Кроме всего прочего, в меню Search появился новый пункт Grep, реализующий поиск по внешним файлам. Этот инструмент выполняет сканирование целого дерева каталогов в поисках файлов с именами, соответствующими шаблону и содержащими указанную строку. Результаты поиска отображаются в новом немодальном окне с прокручиваемым списком, где выводятся имена файлов, номера и содержимое строк с найденными совпадениями. Щелчок на элементе списка открывает соответствующий файл в новом немодальном окне редактирования PyEdit, при этом автоматически выполняется переход к строке с совпадением и ее выделение. В этой реализации повторно используется программный код, написанный нами ранее:
• Утилита find, написанная нами в главе 6, выполняющая обход дерева каталогов
• Реализация списка с прокруткой из главы 9 для отображения результатов поиска
• Конструктор рядов с полями для форм ввода, созданный в главе 10, для создания немодального диалога ввода
• Существующая реализация всплывающего режима использования PyEdit для отображения содержимого файлов по запросу
• Существующий обработчик события перехода в PyEdit для перемещения к строке с найденным совпадением
Поддержка многопоточной модели выполнения в операции поиска. Чтобы избежать блокирования графического интерфейса в процессе поиска, поиск производится в параллельных потоках выполнения. Это позволяет запускать сразу несколько операций поиска и выполнять их параллельно (что особенно полезно, когда поиск выполняется в больших деревьях каталогов, таких как стандартная библиотека Python или полное дерево его исходных текстов). При этом применяются такие приемы и механизмы, как стандартные потоки выполнения, очереди и цикл обработки событий от таймера after, с которыми мы познакомились в главе 10, — потоки-производители, не имеющие отношения к графическому интерфейсу, отыскивают совпадения и помещают их в очередь, которая проверяется главным потоком, управляющим графическим интерфейсом, в цикле обработки событий от таймера.
В данной реализации цикл обработки событий от таймера запускается только при выполнении операции поиска, и для каждого поиска используются отдельный поток, отдельный цикл обработки событий от таймера и отдельная очередь. В одном процессе одновременно может выполняться множество потоков и циклов обработки событий от таймера, связанных с поиском, а также могут существовать другие независимые потоки, очереди и циклы обработки событий от таймера. Например, прикрепляемый компонент PyEdit в программе PyMailGUI, представленной в главе 14, может выполнять собственные операции поиска, в то время как программа PyMailGUI выполняет собственные потоки и очереди, используемые для отправки или приема электронной почты. Каждый цикл обработки событий от таймера управляется независимо от процессора событий tkinter. Из-за упрощенной архитектуры в этом примере не используется универсальная реализация threadtools очереди обработчиков из главы 10. Дополнительные примечания о реализации потоков выполнения в операции поиска ищите в исходных текстах, что приводятся далее, и сравните их с файлом _unthreadedtextEditor.py в дереве примеров, где содержится версия PyEdit без поддержки многопоточной модели выполнения.
Поддержка Юникода. Если внимательно изучить реализацию поиска по внешним файлам, можно заметить, что она позволяет определять кодировку для всего дерева и обрабатывает любые исключения, связанные с ошибками декодирования символов, возникающими как при обработке содержимого файлов, так и при обработке имен файлов во время обхода дерева. Как мы узнали в главах 4 и 6, при открытии текстовых файлов в Python 3.X должны декодироваться с применением кодировки, указанной явно или используемой на текущей платформе по умолчанию. Это представляет определенную проблему для операции поиска по файлам, так как деревья каталогов могут содержать файлы с символами в различных кодировках.
Фактически в Windows в одном дереве каталогов часто можно встретить файлы с содержимым в кодировках ASCII, UTF-8 и UTF-16 (варианты ANSI, Utf-8 и Unicode выбора кодировки в Notepad) и даже в других кодировках, особенно в каталогах, где сохраняются файлы, загруженные из Интернета или полученные по электронной почте. Операция открытия таких файлов с применением кодировки UTF-8 в Python 3.X будет приводить к исключениям, а при открытии их в двоичном режиме программа будет получать закодированный текст, в котором едва ли возможно будет отыскать совпадение с искомой строкой. Технически, чтобы выполнить сравнение, необходимо выполнить декодирование байтов, прочитанных из файла, или закодировать искомую строку в байты. При этом совпадение может быть обнаружено только при использовании согласованных кодировок.
Чтобы обеспечить возможность поиска в деревьях каталогов со смешанными кодировками, диалог Grep открывает файлы в текстовом режиме и позволяет вводить имя кодировки, которая будет использоваться для декодирования содержимого всех файлов в просматриваемом дереве каталогов. Для удобства поле ввода с именем кодировки предварительно заполняется значением по умолчанию для текущей платформы, так как этого часто бывает вполне достаточно. Чтобы выполнить поиск в дереве каталогов с файлами разных типов, пользователи могут выполнить несколько операций поиска, указывая различные имена кодировок. При поиске могут также возникать ошибки декодирования имен файлов, но они практически никак не обрабатываются в текущей версии: предполагается, что имена файлов удовлетворяют соглашениям, принятым в файловой системе на данной платформе, в противном случае это приводит к завершению поиска (дополнительные сведения об утилите find обхода дерева каталогов, повторно используемой здесь, а также о проблемах, связанных с кодированием имен файлов в Python, вы найдете в главах 4 и 6).
Кроме того, реализация операции Grep должна предусматривать обработку исключений, связанных с ошибками декодирования файлов, имена которых соответствуют шаблону, но содержимое не может быть декодировано с применением указанной кодировки, и фактически вообще может не быть текстом. Например, операция поиска в стандартной библиотеке Python 3.1 (как в примере поиска строки %, описанном выше) сталкивается с несколькими файлами, которые не смогли быть корректно декодированы в Windows на моем компьютере и могли бы вызвать крах PyEdit. Двоичные файлы, имена которых по случайности соответствуют шаблону, являются еще более худшим вариантом.
В целом программы могут избежать ошибок, вызванных применением неправильных кодировок, либо обрабатывая исключения, либо открывая файлы в двоичном режиме. Так как операция поиска может оказаться не в состоянии интерпретировать содержимое некоторых файлов как текст вообще, при ее реализации был выбран первый подход. В действительности, открытие даже текстовых файлов в двоичном режиме и чтение из них строк двоичных байтов в версии 3.X имитирует поведение текстовых файлов в версии 2.X и позволяет понять, почему принудительный переход на использование Юникода иногда является благом, — двоичный режим позволяет избежать появления исключений, связанных с декодированием, но сам текст по-прежнему остается закодированным и не может использоваться в привычных операциях. В этом случае операция сравнения может давать неверные результаты. Дополнительные детали, касающиеся поддержки Юникода в реализации диалога Grep, а также описание проблем, связанных с этой поддержкой, и способов их решения, вы найдете в исходном программном коде, который приводится ниже. Дополнительные предложения по улучшению можно найти в главе 19, в описании модуля re — инструмента, который можно использовать для организации поиска по шаблону, а не только по определенной строке.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011