Указание обратных вызовов TkInter в словаре для функции запуска отображения - PullRequest
0 голосов
/ 23 июня 2009

У меня проблемы с созданием функции Python, которая запускает объекты TkInter, с командами, привязанными к кнопкам меню, используя спецификации кнопок, хранящиеся в словаре.

ПОЛОЖЕНИЕ

Я строю графический интерфейс в Python, используя TkInter. Я написал класс Display (основанный на классе GuiMaker в Lutz, "Programming Python"), который должен обеспечивать окно для ввода данных для различных объектов, поэтому команды и отображение любого экземпляра будут различаться. Я хотел бы настроить эти специфичные для сущности команды и отображения в файле словаря. Файл оценивается при запуске сценария, а его словарь передается в функцию запуска при вызове экземпляра Display. Но команды экземпляров не могут найти методы экземпляров, которые я пытаюсь связать с ними.

ТЕХНИЧЕСКИЕ ХАРАКТЕРИСТИКИ В РАБОТАХ ФУНКЦИЙ

Это не проблема, когда экземпляр Display запускается с конфигурациями, указанными в выделенной функции. Например, это прекрасно работает:

def launchEmployee():
    display = ''
    menuBar = [('File', 0, [('Save', 0, (lambda: display.onSave()))])]
    title = 'Employee Data Entry'
    display_args = {'title': title,
                    'menuBar': menuBar}
    display = DisplayScreen(**display_args)

Подклассы DisplayScreen от GuiMaker, у которого есть методы для обработки объекта menuBar для создания меню. У него есть метод onSave ().

После этого экземпляр Display находит и запускает собственный метод onSave () при нажатии кнопки «Сохранить».

СПЕЦИФИКАЦИЯ ИЗ ФАЙЛА СЛОВАРЯ НЕ РАБОТАЕТ

Но это не работает, когда я пытаюсь запустить экземпляр Display из функции запуска, извлекая его спецификацию из словаря, хранящегося в отдельном файле.

config_file:

{'menuBar':[('File', 0, [('Save', 0, (lambda: display.onSave()))])],
 'title': 'Employee Data Entry'}

файл скрипта:

config = eval(open('config_file', 'r').read())

def launchDisplay(config):
    display = ''
    display = DisplayScreen(**config)

Запустите этот способ, нажав кнопку «Сохранить», появится сообщение об отсутствии глобального объекта «display».

ТЕОРИЯ: СЛОВАРЬ СЛУЧАЙНО ОБРАЗИТСЯ К ОБЪЕКТАМ В ОБЛАСТИ ПРИ ЗНАКЕ () ВЫЗОВ

Я предполагаю, что в случае функции 'display' является строковым объектом, отсутствие метода onSave () не является проблемой для присвоения menuBar, потому что его проверка на метод откладывается внутри лямбда-функции , Когда экземпляр Display назначается объекту 'display', это перегружает предыдущее назначение строкового объекта, но Python по-прежнему знает об 'display' и переходит к нему, когда запрашивается его метод onSave ().

Если это так, то случай конфигурации завершается неудачей, поскольку объект 'display' вообще не существует, когда словарь конфигурации создается путем оценки. Это не вызывает ошибку при вызове eval (), потому что, опять же, лямбда-функция скрывает объект от проверки до вызова. Но при вызове Python ищет объект «display» в области видимости в момент вызова eval (), где ничего не находит, а затем сообщает об ошибке.

НО: ВЫДЕЛЕНИЕ EVAL () ВЫЗОВ В ОБЛАСТИ НЕ ПОМОГАЕТ

Но я попытался переместить оценку файла словаря в функцию и после создания строкового объекта 'display', но это тоже не сработало.

SO:

Что здесь происходит?
Как я могу указать методы, которые будут привязаны к командам в словаре, к которому нужно обращаться при создании экземпляра экранного объекта? Действительно, кажется, что лучше указывать эти экраны в файле конфигурации, чем в множестве дублирующих функций.

1 Ответ

0 голосов
/ 23 июня 2009

Когда ваш lambda выполняется, когда применяется область, но проблема немного тоньше.

В первом случае lambda является вложенной функцией launchEmployee, поэтому компилятор Python (когда он компилирует вмещающую функцию) знает, что сканирует свое тело на наличие ссылок на локальные переменные вмещающей функции и формирует замыкание соответствующим образом

Во втором случае eval скрывает вложенную функцию от компилятора Python во время компиляции включающей функции, поэтому он даже не знает , что это функция включения, и что должен образовывать замыкание, или как.

Я предлагаю вам не поиграться с именами, а вместо этого вставить новый объект display в lambda после его создания. Это требует поиска lambda (или более чем одной лямбды), но вы можете сделать это путем систематического (например, рекурсивного) обхода всех элементов в значениях конфигурации, поиска элементов, которые являются экземплярами type(lambda:0), и принятия некоторого соглашения, такого как, «создаваемый виджет в этих лямбдах именуется именем« виджет »и является последним аргументом (со значением по умолчанию)».

Итак, вы измените свой конфигурационный файл на:

'menuBar':[('File', 0, [('Save', 0, (lambda widget=None: widget.onSave()))])],
 'title': 'Employee Data Entry'}

и после display = DisplayScreen(**config) последующая обработка config следующим образом:

def place_widget(widget, mess):
  if isinstance(mess, (list, tuple)):
    for item in mess:
      place_widget(widget, item)
  elif isinstance(mess, type(lambda:0)):
    if mess.func_code.co_varnames[-1:] == ('widget',):
      mess.func_defaults = mess.func_defaults[:-1] + (widget,)

По общему признанию, несколько сложный код, но я не вижу простого способа сделать это в указанном вами контексте eval 'd строки, преобразованной в dict, содержащий некоторые lambda s.

Я бы обычно рекомендовал стандартный библиотечный модуль inspect для такого самоанализа, но так как эта функция постобработки неизбежно должна возиться с func_defaults (inspect только проверяет, не изменяет внутренности объекта подобным образом), Кажется более логичным, чтобы весь код работал на одном и том же, довольно глубоком уровне.

Редактировать : возможен более простой подход, если вы не настаиваете, чтобы виджеты были только локальными переменными, а вместо этого можете сделать их, например, атрибуты глобального объекта.

Итак, на уровне модуля вы говорите:

class Bunch(object): pass
widgets = Bunch()

и в своей функции вы назначаете только что созданный виджет на widgets.foobar вместо присвоения голому имени foobar. Тогда ваш eval ed lambda может выглядеть примерно так:

lambda: widgets.foobar.onSave()

и все будет хорошо, потому что, таким образом, закрытие не требуется (оно только необходимо - и ничего не ожидается из-за eval - в вашем первоначальном подходе для сохранения local переменные, которые вы используете в то время, когда lambda они понадобятся).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...