Саммерфилд - Программирование на Python 3 (1077331), страница 90
Текст из файла (страница 90)
Гог Гс!е 1и деС 11!ее(вув.агдч(1:)): СП = Мопе сгу: ГП = орел(ГС1е, "гЬ") вад1с = Гл.геао(1000) Гог цеС 111е Суре 1п дес 111е Суре Гопсссопв: Гс1есуре = деС 111е Суре(вар!с, ов.раса.вр1ссехс(111е)( 1)) 11 Гс!еСуре 1в пос Мопе: рг1пс("(0..<20)(1)".Согвас((11есуре, 111е)) Ьгеах е1ве: рг1пС("(О:.<20)(1)".Гогваг("Опяпонп", Гс1е)) ехсерС ЕпчсгопвепсЕггог ав егг: ргпп(егг) 11па11у: 11 СЬ св поС Моле: Гя.с1ове() Этот цикл выполняет итерации по всем файлам, перечисленным в командной строке, и читает первые 1000 байтов из каждого.
После этого он пытается вызвать по очереди каждую найденную функцию цеС (11е Суре(), чтобы определить тип текущего файла. Если имя файла 404 Глава 8. Усовершенствованные приемы программирования удается определить, на экран выводится информация о нем, внутренний цикл прерывается и выполняется переход к следующему файлу. Если тип файла определить не удалось или если не удалось найти ни одной функции дег 111е гуре(), выводится текст «Пп]гповгп» (тип неизвестен).
Теперь рассмотрим три разных (но эквивалентных) способа динамического импортирования модулей, начав с самого длинного и самого сложного, поскольку в нем будет явно продемонстрирован каждый этап работы: бег 1оеб вобо1ее(): вобо1еэ = [] гог паве тп ое. 11егб1г(ое.рать,о!главе( гт1е ) ог ". '); гг паве.епбеи!гь(".ру") епб "вес!с" 1п паве 1овег(); Гт1епаве = паве паве = оэ.рать.ер1!гехт(пепе)[д] тг паве.ге1бепг111ег() апб паве пот тп еуэ.вобо1еэ.
ГЬ = попе ггу: ГЬ = орел(111епаве, "г", епообгпд="огт8") собе = Гп.геаб() вобо1е = туре(еуе)(паве) еуе.вобо1ее[пепе] = вобо1е ехео(собе, вобо1е б!ог ) вобо1ее.аррепб(вобо1е) ехсерг (Еп»1гопвепгЕггог, 8упгахЕггог) ае егг; еуе.зобо!ее.рор(пепе, Ноле) рг1пг(егг) !!папу: 1Г ГЬ 1е пог Вопе ГЬ. с1оее() гегогп зобо!ее Функция начинает с того, что запускает итерации по всем файлам, находящимся в каталоге программы. Если это текущий каталог, функция оэ.регп.б!главе( (11е ) вернет пустую строку, что вынудит функцию ое. 1гвгб! г() возбудить исключение; чтобы этого не произошло, в этом случае функции передается строка ".
". Из каждого имени файла-кандидата (который имеет расширение .ру и в имени содержит текст «тпая1с») функция получает имя модуля, отсекая расширение от имени файла. Если получившееся имя является допустимым идентификатором, следовательно, его можно рассматривать как имя модуля. Если это имя еще отсутствует в глобальном списке модулей, который предоставляет словарь эуз.
вобо1еэ, производится попытка импортировать его. После этого выполняется чтение текста из файла в строку собе. Следующая строка, вобо1е = гуре(еуэ) (паве) таит в себе одну хитрость. Когда вызывается функция Суре( ), она возвращает объект типа укаэанного ей объекта. То есть, вызвав суре( ! ), мы получим ! Ьг. Если попытать- Упучшенные приемы процедурного программирования ся вывести объект типа, будет получено нечто удобочитаемое для человека, например, «[пС», но если вызвать объект типа как функцию, будет получен объект данного типа.
Например, в переменную х можно записать целое число б с помощью инструкций х = 5, или х = Сп1(5), или х = гуре(0)(5), или 1пс 1уре = суре(0); х = ся1 суре(5). В нашем случае вызывается функция Суре(зуз), где зуз является модулем, поэтому функция возвращает объект типа для модуля (по сути то же самое, что и объект класса), который может использоваться для создания нового модуля с заданным именем.
Точно так же, как и в примере с типом спС, где не имело значения, какое число используется для получения объекта типа СяС, совершенно не важно, какой модуль будет использоваться (при условии, что он существует, то есть был импортирован) для получения объекта типа модуля. После получения нового (пустого) модуля он добавляется в глобальный список модулей, чтобы предотвратить непреднамеренное повторное его импортирование. Это делается перед вызовом функции ехес(), чтобы как можно ближе имитировать поведение инструкции серогС.
Затем вызывается функция ехес(), которая выполняет программный код, прочитанный из файла; при этом в качестве контекста используется словарь модуля. В конце полученный модуль добавляется в словарь модулей, которые мы будем использовать при определении типов файлов. Если возникли какие-либо проблемы, модуль удаляется из глобального словаря модулей (если он уже был туда добавлен), то есть модуль не будет добавлен в список модулей, если возникнет какая-либо ошибка. Обратите внимание, что функция ехес() может обрабатывать любые объемы программного кода (тогда как функция ехз1( ) в состоянии обработать лишь единственное выражение — смотрите табл.
8.1) и возбуждает исключение 5упсахЕггог в случае обнаружения синтаксической ошибки. Ниже демонстрируется второй способ динамической загрузки модуля во время выполнения программы — программный код, показанный ниже, замещает первый вариант блоком 1гу ... ехсерС: Сгу. ехес("Сзрогг " » саве) зосс1ез.аррепс(зуз.зосс1ез[яазе]) Ехсер1 БупгахЕггог аз егг: ргспС(егг) Одна из теоретических проблем, присущих этому варианту, заключается в его небезопасности. Переменная сазе может начинаться с подстроки зуз;, за которой может следовать некоторый зловредный программный код. Ниже демонстрируется третий вариант, который также замещает пер- вый вариант блоком С гу ... ехсерС: Сгу.
аосс1е = 1зрогС (сазе) 406 Глава 8. Усовершенствованные приемы программирования воз ч1ея. ар реп з(воочЗе) ехсерт (!врогтЕггог, ЕуптахЕггог) ая егг. ргтпт(егг) Синтаксис Описание Импортирует модуль по его имени (подробности приводят- ся в тексте) !врогт (...) Возвращает объект с программным кодом, полученным в результате компиляции исходного текста яочгсе; в аргументе (!1е передается имя файла или Гсятг!пд>"; аргумент всбе может принимать одно из трех значений: ятпд1е', "еча1"или "ехес" совр!1е(яочгсе, Г!1е, воре) Удаляет из объекта оЬЗ атрибут с именем паве Се1аттг(оЬЗ, паве) Возвращает список имен в локальной области видимости или, если определено значение аргумента о01, список имен атрибутов объекта ЬЬЗ (то есть имена его атрибутов и методов) Ь!г(ЬЬЗ) Возвращает результат вычисления единственного выраже- ния яочгсе; если определены значения аргументов д!ЬЬа1я и 1осаЗя (в виде словарей), они будут использованы как гло- бальный и локальный контекст соответственно ечз1(ясчгсе, д1ора1я, 1осг1я) Интерпретирует объект о01, который может быть строкой или объектом программного кода, полученным в результате вызова функции совр!1е(), и возвращает Попе; если определены значения аргументов д1ЬЬа1я и 1оса1я, они будут использованы как глобальный и локальный контекст соот- ветственно ехес(сЬЗ, д1орг!я, 1осаЗя) Возвращает значение атрибута с именем паве, принадлежа- щего объекту оЬЗ, или значение аргумента ча1, если оно оп- ределено и в объекте оЬЗ отсутствует указанный атрибут дстаттг(ОЬЗ, паве, ча1) Возвращает словарь текущего глобального контекста д1о0а1я() Возвращает тгче, если объект оЬЗ имеет атрибут с именем паве Паяажг(оь), паве) Возвращает словарь текущего локального контекста 1оса1я() Устанавливает значение ча1 в атрибуте паве объекта оЬЗ, создавая атрибут, если зто необходимо яетаттг(оЬЗ, паве, ча1) Возвращает объект типа для объекта оЬЗ туре(сьз) чагя(аЬЗ) Возвращает контекст объекта ЬЬЗ в виде словаря или ло.
кальный контекст, если аргумент ЬЬЗ не определен Таблица 8.1. Функции динаггичесного прогро нлирования и интроспенции Улучшенные приемы процедурного программирования 407 Это самый простой способ динамического импортирования модулей, который несколько безопаснее, чем прямое использование функции ехес(), хотя, как и в любом другом случае динамического импортирования, мы ничего не можем говорить о безопасности, потому что заранее неизвестно, какой программный код будет выполнен при импортировании модуля.
Хотя ни один из приемов, продемонстрированных здесь, не работает с пакетами или модулями в других каталогах, совсем несложно дополнить программный код для реализации этой возможности. Если же потребуется нечто более изощренное, следует обратиться к электронной документации, особенно к описанию функции 1врог1 (). Импортировав модуль, можно получить доступ к функциональности, предоставляемой им. реализовать это можно с помощью встроенных функций интроспекции дега1(г() и Пазаггг(). Ниже показано, как они используются в реализации функции де1 (цпс1!оп(): Це( дег Гипс!!оп(воцц1е, Гипс!!оп павЕ): гцпс11оп = де! гипс!!оп.саспе.де1((аоцц1е, гцпс1!оп паве), иоле) 1Г Гцпс11оп ьз Иопе: 1гу; Гцпс11оп = де1аггг(аоцц1е, Гипс!1оп паве) гг пог паза11г(гипс!!оп, " са11 "): га1зе Аггг!ЬцгеЕггог() де! Гцпс11оп.сасПе(аоцц1е, Гипс!поп паве) = Гцпс11оп ехсерг А11г!Ьи1еЕггог: Гипс!!оп = Иопе гЕ1цгп Гцпс11оп де1 гипс!!оп.саспе = () Пока не будем акцентировать внимание на программном коде, выполняющем действия с кзшем.
Эта функция вызывает функцию де(а11г(), передавая ей имя модуля и имя ожидаемой функции. Если указанный атрибут отсутствует, будет возбуждено исключение А11г!ЬцгеЕггог, но если такой атрибут имеется, используется функция Пззз11г(), с помощью которой определяется наличие атрибута са11 у данного атрибута — этот атрибут имеется у всех вызываемых объектов (то есть у функций и методов). (Далее мы познакомимся с более элегантным способом проверить, является ли объект вызываемым.) Если атрибут существует и является вызываемым, его можно вернуть вызывающей программе, в противном случае возвращается Иоле, чтобы показать, что искомая функция недоступна. Когда выполняется обработка нескольких сотен файлов (например, при использовании шаблона *.* в каталоге С:'1ш(пс(ошз), оказывается слишком затратно проверять наличие модуля в каждом файле.
Поэтому 408 Глава 8. Усовершенствованные приемы программирования сразу вслед за заголовком определения функции двг (гзпсг1оп() к ней добавляется атрибут — словарь с именем сасле. (Вообще говоря, язык Ру(Ьоп позволяет добавлять любые атрибуты к любым объектам.) Когда функция дег Гопсшоп() вызывается в первый раз, словарь сасле не содержит ни одного элемента, поэтому метод С! с1. де1( ) вернет йопе. Но всякий раз, когда будет обнаруживаться подходящая функция, в словарь будет помещаться новый элемент, ключом которого является кортеж иэ двух элементов, с именами модуля и функции, а значением— сама функция. Поэтому при втором и последующих вызовах запрошенная функция будет возвращаться прямо из каша, при этом поиск атрибута вообще не будет производиться.' Методика, используемая в функции деГ Гппсшоп() для кэширования возвращаемых значений с заданным набором аргументов, называется запоминанием (тета(эту).
Она может использоваться при реализации любой функции, не имеющей побочных эффектов (не изменяющей никаких глобальных переменных) и всегда возвращающей один и тот же результат при тех же (неизменных) значениях аргументов. Так как программный код, необходимый для создания и управления кэшем любой «запоминающей» функции, остается неизменным, он является прекрасным кандидатом на роль функции-декоратора; некоторые примеры декораторов баево(ые приводятся в справочнике РуФЬоп Соо)сЬоо)с на сайте сог)е.асг(вез»а(е.сот/гес(рез/!апуз/ру»)»оп/. Однако сами объекты модулей изменяемы, поэтому не все готовые к употреблению декораторы баеао1зе смогут работать с нашей функцией «как есть».