Главная страница
Навигация по странице:

  • Динамическое импортирование

  • Синтаксис Описание

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница46 из 74
    1   ...   42   43   44   45   46   47   48   49   ...   74
    401
    Такой способ отлично подходит для случая, когда выражение вводит
    ся пользователем, но как быть, если необходимо создать функцию ди
    намически? Для этой цели можно использовать встроенную функцию exec()
    . Например, пользователь может ввести формулу, такую как
    4
    πr
    2
    , и ее название – «area of sphere» (площадь поверхности шара), ко
    торую необходимо преобразовать в функцию. Предположим, что
    π мы заменили на math.pi; тогда функция, которую требуется создать, могла бы выглядеть, как показано ниже:
    import math code = '''
    def area_of_sphere(r):
    return 4 * math.pi * r ** 2
    '''
    context = {}
    context["math"] = math exec(code, context)
    Мы должны использовать надлежащие отступы, потому что указан
    ный программный код должен соответствовать требованиям языка Py
    thon. (Хотя в данном случае мы могли бы записать весь программный код в одной строке, потому что блок функции состоит всего из одной строки.)
    Если функции exec() в виде единственного аргумента передать некото
    рый программный код, у нас не будет возможности получить доступ к какимлибо функциям или переменным, созданным в результате выполнения этого программного кода. Кроме того, программный код,
    выполняемый функцией exec(), не имеет доступа к импортированным модулям, переменным функциям и к другим объектам, которые нахо
    дятся в области видимости в момент вызова. Обе эти проблемы реша
    ются посредством передачи словаря во втором аргументе. Словарь обеспечивает место, где будут сохраняться ссылки на объекты, кото
    рые будут доступны после того, как функция exec() вернет управле
    ние. Например, использование словаря context означает, что после вы
    зова функции exec() в словаре появится ссылка на объект функции area_of_sphere()
    , созданной в результате вызова exec(). В данном при
    мере нам необходимо, чтобы программный код, выполняемый функ
    цией exec(), обладал доступом к модулю math, поэтому мы добавили в словарь context элемент, ключом которого является имя модуля,
    а значением – ссылка на объект модуля. Тем самым мы обеспечили доступность объекта math.pi для программного кода, выполняемого функцией exec().
    В некоторых случаях бывает удобно передать функции exec() весь гло
    бальный контекст. Сделать это можно, использовав словарь, возвра
    щаемый функцией globals(). Недостаток такого подхода состоит в том,
    что любые объекты, создаваемые вызовом функции exec(), будут добав
    лены в глобальный словарь. Решить эту проблему можно, скопировав глобальный контекст в словарь, например, context = globals().copy().

    402
    Глава 8. Усовершенствованные приемы программирования
    Такой прием обеспечит программному коду, выполняемому функцией exec()
    , доступ ко всем импортированным модулям, переменным и дру
    гим объектам, имеющимся в области видимости, но любые изменения контекста, производимые в функции exec(), будут сохраняться в сло
    варе context и не затронут глобальное окружение. (Может показаться,
    что надежнее было бы выполнять копирование с помощью функции copy.deepcopy()
    , но если проблема обеспечения безопасности стоит ост
    ро, то лучше вообще отказаться от использования функции exec().)
    Точно так же существует возможность передавать локальный кон
    текст, например, передавая результат вызова функции locals() в треть
    ем аргументе – она обеспечивает программному коду, выполняемому функцией exec(), доступ к объектам, созданным в локальной области видимости.
    После вызова функции exec() словарь context будет содержать ключ "area_of_sphere"
    , значением которого будет функция area_of_sphere().
    Ниже показано, как можно получить доступ к этой функции и вы
    звать ее:
    area_of_sphere = context["area_of_sphere"]
    area = area_of_sphere(5) # area == 314.15926535897933
    Объект area_of_sphere – это ссылка на объект функции, созданной ди
    намически, которая может использоваться как любая другая функ
    ция. Несмотря на то, что в этом примере была создана единственная функция, тем не менее, в отличие от функции eval(), которая может интерпретировать единственное выражение, функция exec() может выполнять любое число инструкций языка Python, включая целые мо
    дули, как будет показано в следующем подразделе.
    Динамическое импортирование
    В языке Python имеются три простых механизма, которые могут ис
    пользоваться для создания модулей расширения, причем все они свя
    заны с импортированием модулей во время выполнения. Как только будет выполнено динамическое импортирование дополнительных мо
    дулей, можно с помощью функций интроспекции, входящих в состав языка Python, проверить доступность требуемых функциональных возможностей и задействовать их.
    В этом подразделе мы рассмотрим программу magicnumbers.py. Эта программа считывает первые 1000 байтов из каждого файла, указан
    ного в командной строке, и для каждого из них выводит его тип (или текст «Unknown» (тип неизвестен)) и имя. Ниже приводится пример командной строки и фрагмент вывода программы:
    C:\Python30\python.exe magicnumbers.py c:\windows\*.*
    XML.................c:\windows\WindowsShell.Manifest
    Unknown.............c:\windows\WindowsUpdate.log
    Windows Executable..c:\windows\winhelp.exe

    Улучшенные приемы процедурного программирования
    403
    Windows Executable..c:\windows\winhlp32.exe
    Windows BMP Image...c:\windows\winnt.bmp
    Программа пытается загрузить все модули, находящиеся в том же ка
    талоге, что и программа, имя файла которых содержит слово «magic».
    Такие модули, как ожидается, содержат единственную общедоступ
    ную функцию с именем get_file_type(). В состав примеров к книге вхо
    дят два очень простых модуля, StandardMagicNumbers.py и Windows
    MagicNumbers.py
    , каждый из которых экспортирует функцию get_fi
    le_type()
    Мы будем рассматривать функцию main() программы, разделив ее на две части:
    def main():
    modules = load_modules()
    get_file_type_functions = []
    for module in modules:
    get_file_type = get_function(module, "get_file_type")
    if get_file_type is not None:
    get_file_type_functions.append(get_file_type)
    Вскоре мы увидим три различные реализации функции load_modules(),
    возвращающей (возможно, пустой) список объектов модулей, а затем рассмотрим функцию get_function(). Для каждого найденного модуля мы попробуем получить доступ к функции get_file_type() и добавим все такие функции в список.
    for file in get_files(sys.argv[1:]):
    fh = None try:
    fh = open(file, "rb")
    magic = fh.read(1000) for get_file_type in get_file_type_functions:
    filetype = get_file_type(magic,
    os.path.splitext(file)[1])
    if filetype is not None:
    print("{0:.<20}{1}".format(filetype, file))
    break else:
    print("{0:.<20}{1}".format("Unknown", file))
    except EnvironmentError as err:
    print(err)
    finally:
    if fh is not None:
    fh.close()
    Этот цикл выполняет итерации по всем файлам, перечисленным в ко
    мандной строке, и читает первые 1000 байтов из каждого. После этого он пытается вызвать по очереди каждую найденную функцию get_
    file_type()
    , чтобы определить тип текущего файла. Если имя файла

    404
    Глава 8. Усовершенствованные приемы программирования удается определить, на экран выводится информация о нем, внутрен
    ний цикл прерывается и выполняется переход к следующему файлу.
    Если тип файла определить не удалось или если не удалось найти ни одной функции get_file_type(), выводится текст «Unknown» (тип не
    известен).
    Теперь рассмотрим три разных (но эквивалентных) способа динамиче
    ского импортирования модулей, начав с самого длинного и самого сложного, поскольку в нем будет явно продемонстрирован каждый этап работы:
    def load_modules():
    modules = []
    for name in os.listdir(os.path.dirname(__file__) or "."):
    if name.endswith(".py") and "magic" in name.lower():
    filename = name name = os.path.splitext(name)[0]
    if name.isidentifier() and name not in sys.modules:
    fh = None try:
    fh = open(filename, "r", encoding="utf8")
    code = fh.read()
    module = type(sys)(name)
    sys.modules[name] = module exec(code, module.__dict__)
    modules.append(module)
    except (EnvironmentError, SyntaxError) as err:
    sys.modules.pop(name, None)
    print(err)
    finally:
    if fh is not None:
    fh.close()
    return modules
    Функция начинает с того, что запускает итерации по всем файлам, на
    ходящимся в каталоге программы. Если это текущий каталог, функ
    ция os.path.dirname(__file__) вернет пустую строку, что вынудит функцию os.listdir() возбудить исключение; чтобы этого не произош
    ло, в этом случае функции передается строка ".". Из каждого имени файлакандидата (который имеет расширение .py и в имени содержит текст «magic») функция получает имя модуля, отсекая расширение от имени файла. Если получившееся имя является допустимым иденти
    фикатором, следовательно, его можно рассматривать как имя модуля.
    Если это имя еще отсутствует в глобальном списке модулей, который предоставляет словарь sys.modules, производится попытка импортиро
    вать его.
    После этого выполняется чтение текста из файла в строку code. Сле
    дующая строка, module = type(sys)(name) таит в себе одну хитрость. Ко
    гда вызывается функция type(), она возвращает объект типа указанно
    го ей объекта. То есть, вызвав type(1), мы получим int. Если попытать

    Улучшенные приемы процедурного программирования
    405
    ся вывести объект типа, будет получено нечто удобочитаемое для чело
    века, например, «int», но если вызвать объект типа как функцию,
    будет получен объект данного типа. Например, в переменную x можно записать целое число 5 с помощью инструкций x = 5, или x = int(5),
    или x = type(0)(5), или int_type = type(0); x = int_type(5). В нашем случае вызывается функция type(sys), где sys является модулем, по
    этому функция возвращает объект типа для модуля (по сути то же са
    мое, что и объект класса), который может использоваться для созда
    ния нового модуля с заданным именем. Точно так же, как и в примере с типом int, где не имело значения, какое число используется для по
    лучения объекта типа int, совершенно не важно, какой модуль будет использоваться (при условии, что он существует, то есть был импорти
    рован) для получения объекта типа модуля.
    После получения нового (пустого) модуля он добавляется в глобаль
    ный список модулей, чтобы предотвратить непреднамеренное повтор
    ное его импортирование. Это делается перед вызовом функции exec(),
    чтобы как можно ближе имитировать поведение инструкции import.
    Затем вызывается функция exec(), которая выполняет программный код, прочитанный из файла; при этом в качестве контекста использу
    ется словарь модуля. В конце полученный модуль добавляется в сло
    варь модулей, которые мы будем использовать при определении типов файлов. Если возникли какиелибо проблемы, модуль удаляется из глобального словаря модулей (если он уже был туда добавлен), то есть модуль не будет добавлен в список модулей, если возникнет какаялибо ошибка. Обратите внимание, что функция exec() может обрабатывать любые объемы программного кода (тогда как функция eval() в состоя
    нии обработать лишь единственное выражение – смотрите табл. 8.1)
    и возбуждает исключение SyntaxError в случае обнаружения синтакси
    ческой ошибки.
    Ниже демонстрируется второй способ динамической загрузки модуля во время выполнения программы – программный код, показанный ни
    же, замещает первый вариант блоком try ... except:
    try:
    exec("import " + name)
    modules.append(sys.modules[name])
    except SyntaxError as err:
    print(err)
    Одна из теоретических проблем, присущих этому варианту, заключа
    ется в его небезопасности. Переменная name может начинаться с под
    строки sys;, за которой может следовать некоторый зловредный про
    граммный код.
    Ниже демонстрируется третий вариант, который также замещает пер
    вый вариант блоком try ... except:
    try:
    module = __import__(name)

    406
    Глава 8. Усовершенствованные приемы программирования modules.append(module)
    except (ImportError, SyntaxError) as err:
    print(err)
    Таблица 8.1. Функции динамического программирования и интроспекции
    Синтаксис
    Описание
    __import__(...)
    Импортирует модуль по его имени (подробности приводят
    ся в тексте)
    compile(source,
    file,
    mode)
    Возвращает объект с программным кодом, полученным в результате компиляции исходного текста source; в аргу
    менте file передается имя файла или ""; аргумент mode может принимать одно из трех значений: "single",
    "eval"
    или "exec"
    delattr(obj,
    name)
    Удаляет из объекта obj атрибут с именем name dir(obj)
    Возвращает список имен в локальной области видимости или, если определено значение аргумента obj, список имен атрибутов объекта obj (то есть имена его атрибутов и методов)
    eval(source,
    globals,
    locals)
    Возвращает результат вычисления единственного выраже
    ния source; если определены значения аргументов globals
    и locals (в виде словарей), они будут использованы как гло
    бальный и локальный контекст соответственно exec(obj,
    globals,
    locals)
    Интерпретирует объект obj, который может быть строкой или объектом программного кода, полученным в результа
    те вызова функции compile(), и возвращает None; если опре
    делены значения аргументов globals и locals, они будут ис
    пользованы как глобальный и локальный контекст соот
    ветственно getattr(obj,
    name, val)
    Возвращает значение атрибута с именем name, принадлежа
    щего объекту obj, или значение аргумента val, если оно оп
    ределено и в объекте obj отсутствует указанный атрибут globals()
    Возвращает словарь текущего глобального контекста hasattr(obj,
    name)
    Возвращает True, если объект obj имеет атрибут с именем name locals()
    Возвращает словарь текущего локального контекста setattr(obj,
    name, val)
    Устанавливает значение val в атрибуте name объекта obj,
    создавая атрибут, если это необходимо type(obj)
    Возвращает объект типа для объекта obj vars(obj)
    Возвращает контекст объекта obj в виде словаря или ло
    кальный контекст, если аргумент obj не определен

    Улучшенные приемы процедурного программирования
    407
    Это самый простой способ динамического импортирования модулей,
    который несколько безопаснее, чем прямое использование функции exec()
    , хотя, как и в любом другом случае динамического импортиро
    вания, мы ничего не можем говорить о безопасности, потому что зара
    нее неизвестно, какой программный код будет выполнен при импорти
    ровании модуля.
    Хотя ни один из приемов, продемонстрированных здесь, не работает с пакетами или модулями в других каталогах, совсем несложно допол
    нить программный код для реализации этой возможности. Если же по
    требуется нечто более изощренное, следует обратиться к электронной документации, особенно к описанию функции __import__().
    Импортировав модуль, можно получить доступ к функциональности,
    предоставляемой им. Реализовать это можно с помощью встроенных функций интроспекции getattr() и hasattr(). Ниже показано, как они используются в реализации функции get_function():
    def get_function(module, function_name):
    function = get_function.cache.get((module, function_name), None)
    if function is None:
    try:
    function = getattr(module, function_name)
    if not hasattr(function, "__call__"):
    raise AttributeError()
    get_function.cache[module, function_name] = function except AttributeError:
    function = None return function get_function.cache = {}
    Пока не будем акцентировать внимание на программном коде, выполняющем действия с кэшем. Эта функция вы
    зывает функцию getattr(), передавая ей имя модуля и имя ожидаемой функции. Если указанный атрибут от
    сутствует, будет возбуждено исключение AttributeError,
    но если такой атрибут имеется, используется функция hasattr()
    , с помощью которой определяется наличие ат
    рибута __call__ у данного атрибута – этот атрибут имеет
    ся у всех вызываемых объектов (то есть у функций и ме
    тодов). (Далее мы познакомимся с более элегантным спо
    собом проверить, является ли объект вызываемым.) Ес
    ли атрибут существует и является вызываемым, его можно вернуть вызывающей программе, в противном случае возвращается None, чтобы показать, что искомая функция недоступна.
    Когда выполняется обработка нескольких сотен файлов (например,
    при использовании шаблона *.* в каталоге C:\windows), оказывается слишком затратно проверять наличие модуля в каждом файле. Поэтому
    Класс
    col
    lections.
    Callable,
    стр. 453

    408
    Глава 8. Усовершенствованные приемы программирования сразу вслед за заголовком определения функции get_function() к ней добавляется атрибут – словарь с именем cache. (Вообще говоря, язык
    Python позволяет добавлять любые атрибуты к любым объектам.) Ко
    гда функция get_function() вызывается в первый раз, словарь cache не содержит ни одного элемента, поэтому метод dict.get() вернет None. Но всякий раз, когда будет обнаруживаться подходящая функция, в сло
    варь будет помещаться новый элемент, ключом которого является кор
    теж из двух элементов, с именами модуля и функции, а значением –
    сама функция. Поэтому при втором и последующих вызовах запро
    шенная функция будет возвращаться прямо из кэша, при этом поиск атрибута вообще не будет производиться.
    1
    Методика, используемая в функции get_function() для кэширования возвращаемых значений с заданным набором аргументов, называется
    запоминанием
    (memoizing). Она может использоваться при реализа
    ции любой функции, не имеющей побочных эффектов (не изменяю
    щей никаких глобальных переменных) и всегда возвращающей один и тот же результат при тех же (неизменных) значениях аргументов.
    Так как программный код, необходимый для создания и управления кэшем любой «запоминающей» функции, остается неизменным, он является прекрасным кандидатом на роль функциидекоратора; неко
    торые примеры декораторов @memoize приводятся в справочнике Py
    thon Cookbook на сайте code.activestate.com/recipes/langs/python/. Од
    нако сами объекты модулей изменяемы, поэтому не все готовые к употреблению декораторы @memoize смогут работать с нашей функци
    ей «как есть». Простое решение этой проблемы состоит в том, чтобы в составе кортежа, применяемого в качестве ключа, использовать не сам модуль, а значение атрибута __name__ модуля.
    Импортировать модули динамически очень просто, и также просто выполнять произвольный программный код на языке
    Python с помощью функции exec(). Это может быть очень удоб
    но, например, когда программный код хранится в базе данных.
    Однако при этом отсутствует какойлибо контроль над импор
    тируемым или выполняемым программным кодом. Вспомни
    те, что, помимо дополнительных переменных, функций и классов, модули могут также содержать программный код,
    выполняемый при импортировании. Если такой программный код поступает из непроверенных источников, он может доста
    вить массу неприятностей. Выбор решения зависит от конкрет
    ной ситуации – это не может быть поводом для беспокойства в определенных случаях или в личных проектах.
    1
    В программе magicnumbers.py наряду с показанной здесь функцией get_
    function()
    присутствует более сложная ее реализация, которая эффектив
    нее обрабатывает модули, в которых отсутствуют искомые функциональ
    ные возможности.

    Улучшенные приемы процедурного программирования
    1   ...   42   43   44   45   46   47   48   49   ...   74


    написать администратору сайта