Следующий прием программирования, который мы рассмотрим, касается изменения графического интерфейса в процессе его работы. Функция imp.reload в языке Python позволяет динамически изменять и перезагружать модули программы, не останавливая ее. Например, можно вызвать текстовый редактор, изменить отдельные части системы во время ее выполнения и увидеть, как проявляются эти изменения, сразу после перезагрузки измененного модуля.
Это мощная возможность, особенно при разработке программ, перезапуск которых мог бы занять длительное время. Программы, которые подключаются к базам данных или сетевым серверам, инициализируют крупные объекты или проходят длинную последовательность шагов, чтобы снова запустить обработчик, являются первыми кандидатами на использование функции reload. Эта функция может существенно сократить время разработки.
Однако в графическом интерфейсе при регистрации обработчиков сохраняются ссылки на объекты, а не имена модулей и объектов, поэтому перезагрузка функций обработчиков после их регистрации не даст желаемого эффекта. Операция imp.reload действует путем изменения содержимого объекта модуля в памяти. Однако, так как библиотека tkinter запоминает указатель на зарегистрированный объект обработчика, ей неизвестно о перезагрузке модуля, в котором находится обработчик. Это означает, что tkinter по-прежнему будет ссылаться на старые объекты модуля, даже если модуль был изменен и перезагружен.
Это тонкий момент, но в действительности достаточно только запомнить, что для динамической перезагрузки функций обработчиков требуется выполнить особые действия. Необходимо не только явно выполнить перезагрузку измененных модулей, но и предоставить некоторый косвенный слой, маршрутизирующий обратные вызовы от зарегистрированных объектов в модули, чтобы перезагрузка возымела эффект.
Например, сценарий в примере 10.14 выполняет дополнительные действия, перенаправляя обратные вызовы функциям в явно перезагруженном модуле. Обработчики, зарегистрированные в библиотеке tkinter, являются объектами методов, которые всего лишь осуществляют перезагрузку и снова отправляют вызов. Так как доступ к действительным функциям обработчиков происходит через объект модуля, перезагрузка этого модуля приводит к обращению к последним версиям этих функций.
Пример 10.14. PP4E\Gui\Tools\rad.py
# перезагружает обработчики динамически
from tkinter import *
import radactions # получить первоначальные обработчики
from imp import reload # в Python 3.X была перемещена в модуль imp
class Hello(Frame):
def __init__(self, master=None):
Frame.__init__(self, master) self.pack() self.make_widgets()
def make_widgets(self):
Button(self, text=’message1’, command=self.message1).pack(side=LEFT)
Button(self, text=’message2’, command=self.message2).pack(side=RIGHT)
def message1(self):
reload(radactions) # перезагрузить модуль radactions перед вызовом radactions.message1() # теперь щелчок на кнопке вызовет новую версию
def message2(self): reload(radactions) # изменения в radactions.py возымеют эффект
# благодаря перезагрузке
radactions.message2(self) # вызовет свежую версию; передать self
def method1(self):
print(‘exposed method…’) # вызывается из функции в модуле radactions
Hello().mainloop()
Если запустить этот сценарий, он создаст окно с двумя кнопками, вызывающими методы message1 и message2. Пример 10.15 содержит фактическую реализацию обработчика. Его функции получают аргумент self, который обеспечивает доступ к объекту класса Hello, как если бы это были действительные методы. Можно многократно изменять этот файл во время выполнения сценария rad; каждое такое действие изменяет поведение графического интерфейса при нажатии кнопки.
Пример 10.15. PP4E\Gui\Tools\radactions.py
# обработчики: перезагружаются перед каждым вызовом
def message1(): # изменить себя
print(‘spamSpamSPAM’) # можно было бы вывести диалог…
def message2(self):
print(‘Ni! Ni!’) # изменить себя
self.method1() # обращение к экземпляру ‘Hello’…
Попробуйте запустить сценарий rad и изменять сообщения, которые выводит radactions, в другом окне. Вы должны увидеть, как при нажатии кнопок в окно консоли будут выводиться новые сообщения. Данный пример намеренно сделан простым, для иллюстрирации идеи, но на практике перезагружаемые таким способом операции могут выводить диалоги, новые окна верхнего уровня и так далее. Перезагрузка программного кода, создающего такие окна, позволяет динамически изменять их внешний вид.
Существуют и другие способы изменения графического интерфейса во время его выполнения. Например, в главе 9 мы видели, что внешний вид в любой момент можно изменить, вызвав метод config виджета, а сами виджеты можно динамически добавлять и удалять с экрана такими методами, как pack_forget и pack (и родственными им для менеджера компоновки grid). Кроме того, передача нового значения параметра command=action методу config может динамически установить в качестве обработчика обратного вызова новый вызываемый объект — при наличии соответствующей поддержки это может оказаться реальной альтернативой использованной выше обходной схеме повышения эффективности перезагрузки в графических интерфейсах.
Разумеется, далеко не все графические интерфейсы должны быть настолько динамичными. Однако представьте себе игру, которая позволяет модифицировать персонажи, — динамическая перезагрузка в таких системах может оказаться очень полезной. (Я оставляю задачу расширения этого примера на многопользовательские ролевые игры как самостоятельное упражнение.)
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011