Прежде чем двинуться дальше, хочу отметить, что большая часть перечисленных выше преимуществ от создания компонентов на основе классов может быть получена в результате создания автономных классов, не являющихся производными от класса Frame или других классов виджетов из библиотеки tkinter. Так, класс из примера 7.24 создает окно, изображенное на рис. 7.23.
Пример 7.24. PP4E\Gui\Intro\gui7.py
from tkinter import *
class HelloPackage: # не является подклассом виджета
def __init__(self, parent=None):
self.top = Frame(parent) # встроить фрейм Frame self.top.pack() self.data = 0
self.make_widgets() # прикрепить виджеты к self.top
def make_widgets(self):
Button(self.top, text=’Bye’, command=self.top.quit).pack(side=LEFT) Button(self.top, text=’Hye’, command=self.message).pack(side=RIGHT)
def message(self): self.data += 1 print(‘Hello number’, self.data)
if __name__ == ‘__main__’: HelloPackage().top.mainloop()
Рис. 7.23. Автономный класс в действии
Если запустить этот сценарий, кнопка Hye будет производить вывод в stdout, а Bye — закрывать окно и завершать работу программы, как и раньше:
C:\…\PP4E\Gui\Intro> python gui7.py
Hello number 1
Hello number 2
Hello number 3
Hello number 4
Так же как и раньше, атрибут self.data сохраняет информацию о состоянии между событиями, а для обработки событий вызывается метод self.message, имеющийся в этом классе. Но в отличие от того, что было раньше, сам класс HelloPackage не является подклассом виджета Frame.
Он вообще не является подклассом какого-либо виджета — он служит только для создания пространства имен, хранящего действительные объекты виджетов и информацию о состоянии. По этой причине виджеты прикрепляются к объекту self.top (встроенному фрейму Frame), а не к self. Более того, все ссылки на объект как на виджет должны передаваться вниз, встроенному фрейму, как, например, вызов метода top. mainloop для запуска интерфейса в конце сценария.
Это приводит к тому, что внутри класса приходится писать чуть больше программного кода, но устраняет возможные конфликты имен между атрибутами, добавляемыми к self в структуре tkinter и существующими методами виджетов tkinter. Например, если определить в своем классе метод config, он замаскирует вызов config, экспортируемый библиотекой tkinter. Для автономных классов, как в этом примере, доступны будут только те методы и атрибуты экземпляров, которые определены в этих классах.
В действительности в библиотеке tkinter используется не очень много имен, поэтому обычно это не создает больших проблем.[XXXII] Конечно, такая вероятность существует, но, честно говоря, за 18 лет программирования на языке Python я не встречался с конфликтами имен tkinter в подклассах виджетов. Кроме того, использование автономных классов не лишено своих недостатков. Хотя в целом их можно прикреплять и создавать производные от них классы, они не вполне совместимы с действительными объектами виджетов. Например, вызовы настройки, выполняемые в примере 7.21 для подкласса Frame, будут терпеть неудачу в примере 7.25.
Пример 7.25. PP4E\Gui\Intro\gui7b.py
from tkinter import *
from gui7 import HelloPackage # или from gui7c, где добавлен __getattr__
frm = Frame()
frm.pack()
Label(frm, text=’hello’).pack()
part = HelloPackage(frm)
part.pack(side=RIGHT) # НЕУДАЧА! Должно быть part.top.pack(side=RIGHT) frm.mainloop()
Этот сценарий вообще не будет работать, потому что part не является настоящим виджетом. Чтобы работать с ним как с виджетом, нужно спуститься в part.top, прежде чем настраивать интерфейс, и рассчитывать на то, что имя top не будет изменено разработчиком класса. Другими словами, требуется знать внутреннее устройство класса. Лучше всего эти действия реализовать в самом классе, определив метод, всегда направляющий обращения к неизвестным атрибутам встроенному объекту класса Frame, как показано в примере 7.26.
Пример 7.26. PP4E\Gui\Intro\gui7c.py
import gui7
from tkinter import *
class HelloPackage(gui7.HelloPackage):
def __getattr__(self, name):
return getattr(self.top, name) # передать вызов настоящему виджету
if __name__ == ‘__main__’: HelloPackage().mainloop() # вызовет __getattr__!
Этот сценарий создает такое же окно, как на рис. 7.23. Однако изменения в примере 7.25, выражающиеся в импортировании расширенной версии класса HelloPackage из модуля gui7c, обеспечивают корректную работу интерфейса, изображенного на рис. 7.24.
Рис. 7.24. Автономный класс в действии
Перенаправление операций обращения к атрибутам вложенных виджетов обеспечивает нормальную работу этого примера, но при этом требуется писать еще больше программного кода в автономных классах. Впрочем, как обычно, значимость всех таких компромиссов зависит от конкретного приложения.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011