DroidScript
DroidScript
учимся и разрабатываем

Python: динамическое создание объектов и кода

14.12.2017

Для начала определимся с тем, что подразумевается под динамическим созданием объектов. Одни подразумевают под этим создание объектов программным образом (при помощи кода в визуальных языках программирования), другие - создание нескольких экземпляров объектов одного типа в цикле. Мы же будем понимать под динамическим созданием объектов возможность создания их из текстовой строки. Для чего это нужно? Рассмотрим пару вариантов создания кнопки с использованием PyQt5:

from PyQt5.QtWidgets import QPushButton

myButton = QPushButton('Кнопка')
myButton.myProp = 100

или

from PyQt5.QtWidgets import QPushButton

myClass = type('My', (QPushButton,), {'myProp':'100'})
btn = myClass("Кнопка")

В обоих примерах кнопке добавлено новое свойство myProp, но добавить его при помощи кода myButton["myProp"] = 100, как в JavaScript, не выйдет. То есть, строгость и упорядоченность языка Python несколько преувеличена. Каждый новый язык программирования рассматривается либо сквозь стекло своего первого языка программирования, либо сквозь призму уже изученных и любимых технологий. Преимущество Python как раз и состоит в том, что на него можно смотреть сквозь призму, например, и С, и Java, и JavaScript, выбирая для работы с ним тот или иной подход.

Название суперкласса QPushButton, экземпляр которого мы хотим создать, задано жестко его названием. Это повышает надёжность кода, но снижает его гибкость. Если необходимо создать объекты разных классов - кнопок, флажков, текстовых полей и др., то придётся либо создавать отдельные функции для них, либо одну многовариантную функцию. Избежать этого можно использованием универсального конструктора объектов, которому в качестве аргумента передаётся строка с названием класса.

Для доступа к объектам в Python существует метод globals(), возвращающий словарь (ассоциативный список) существующих глобальных объектов. Например, обращение к существующему объекту myVar происходит так:

globals()['myVar']

Это запись чем-то напоминает работу с глобальным объектом window в браузерном JavaScript. Отличие состоит в том, что globals() также возвращает и типы, которые в Python также являются объектами, что позволяет создавать объекты следующим образом:

from PyQt5.QtWidgets import QCheckBox

myClass = globals()["QCheckBox"];
checkBox = myClass("Флажок")

или так:

import sys
from PyQt5.QtWidgets import QCheckBox

btn = getattr(sys.modules['__main__'], "QCheckBox")()

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

mod = __import__('PyQt5.QtWidgets', fromlist=['QCheckBox'])
myClass = getattr(mod, 'QCheckBox')
chk = myClass('Флажок')

В первой строке происходит подключение библиотеки класса, а метод getattr возвращает класс, объект которого создаётся в поcледней строке.

Рефлексия в Python заметно проще, чем в Java, но нам требуется больше - интерпретация из строки не только объектов, но и кода. Для этого в Python есть методы eval, exec и compile. Метод exec как раз и предназначен для выполнения кода, заданного в строке:

exec("""
mod = __import__('PyQt5.QtWidgets', fromlist=['QCheckBox'])
myClass = getattr(mod, 'QCheckBox')
chk = myClass('Флажок')
""")
Для отображения флажка chk на экране его необходимо добавить какому-нибудь компоновщику, но эта операция вызовет исключение, так как переменная chk имеет локальную область видимости и недоступна за пределами exec. Для доступа к ней необходимо передать дополнительный аргумент, посредством которого она будет доступна глобально:

gl = {}

exec("""
  mod = __import__('PyQt5.QtWidgets', fromlist=['QCheckBox'])
  myClass = getattr(mod, 'QCheckBox')
  chk = klass('myClass')
""",gl)

gl["chk"].setText("Новый флажок")

Задача генерации кода из строки решена.

Как видно, динамическая работа к объектами и кодом происходит практически так же просто, как в JavaScript и QML. Более того, Python предоставляет разработчику несколько вариантов решения одной и той же задачи, что весьма неплохо.

Динамическую генерацию кода можно использовать, например, при создании своего редактора кода или реализации живого просмотра, который будет неплохим дополнением интегрированной среды разработки IDLE, которая обладает весьма скромной функциональностью по сравнению с другими известными средами разработки.

© 2016-2024 
actech