Поиск функций, определенных в with:Блокировать
-
12-09-2019 - |
Вопрос
Вот некоторый код из Блог Ричарда Джонса:
with gui.vertical:
text = gui.label('hello!')
items = gui.selection(['one', 'two', 'three'])
with gui.button('click me!'):
def on_click():
text.value = items.value
text.foreground = red
Мой вопрос:как, черт возьми, он это сделал?Как диспетчер контекста может получить доступ к области внутри блока with?Вот базовый шаблон, позволяющий попытаться это понять:
from __future__ import with_statement
class button(object):
def __enter__(self):
#do some setup
pass
def __exit__(self, exc_type, exc_value, traceback):
#XXX: how can we find the testing() function?
pass
with button():
def testing():
pass
Решение
Вот один из способов:
from __future__ import with_statement
import inspect
class button(object):
def __enter__(self):
# keep track of all that's already defined BEFORE the `with`
f = inspect.currentframe(1)
self.mustignore = dict(f.f_locals)
def __exit__(self, exc_type, exc_value, traceback):
f = inspect.currentframe(1)
# see what's been bound anew in the body of the `with`
interesting = dict()
for n in f.f_locals:
newf = f.f_locals[n]
if n not in self.mustignore:
interesting[n] = newf
continue
anf = self.mustignore[n]
if id(newf) != id(anf):
interesting[n] = newf
if interesting:
print 'interesting new things: %s' % ', '.join(sorted(interesting))
for n, v in interesting.items():
if isinstance(v, type(lambda:None)):
print 'function %r' % n
print v()
else:
print 'nothing interesting'
def main():
for i in (1, 2):
def ignorebefore():
pass
with button():
def testing(i=i):
return i
def ignoreafter():
pass
main()
Редактировать:еще немного растянул код, добавил некоторые пояснения...:
Перехват местных жителей звонящего по адресу __exit__
это просто - сложнее избежать тех локальных переменных, которые уже были определены до тот with
блок, поэтому я добавил в main две локальные функции, которые with
следует игнорировать.Я не на 100% доволен этим решением, которое выглядит немного сложным, но я не смог провести правильную проверку равенства ни с одним из них. ==
или is
, поэтому я прибег к этому довольно сложному подходу.
Я также добавил цикл (чтобы более точно убедиться, что def
s до/внутри/после обрабатываются должным образом), а также проверку типов и вызов функций, чтобы убедиться в правильности воплощения testing
тот, который идентифицирован (кажется, все работает нормально) - конечно, написанный код работает только в том случае, если def
внутри with
для функции, вызываемой без аргументов, получить подпись с помощью inspect
чтобы этого избежать (но поскольку я вызываю только с целью проверки идентификации правильных функциональных объектов, я не стал беспокоиться об этом последнем уточнении ;-).
Другие советы
Отвечая на ваш вопрос, да, это фрейм-интроспекция.
Но синтаксис, который я бы создал, чтобы сделать то же самое:
with gui.vertical:
text = gui.label('hello!')
items = gui.selection(['one', 'two', 'three'])
@gui.button('click me!')
class button:
def on_click():
text.value = items.value
text.foreground = red
Вот я бы реализовал gui.button
как декоратор, который возвращает экземпляр кнопки с учетом некоторых параметров и событий (хотя теперь мне кажется, что button = gui.button('click me!', mybutton_onclick
тоже нормально).
я бы тоже ушел gui.vertical
таким, какой он есть, поскольку его можно реализовать без самоанализа.Я не уверен в его реализации, но это может включать настройку gui.direction = gui.VERTICAL
так что gui.label()
а другие используют его для вычисления своих координат.
Теперь, когда я смотрю на это, я думаю, что попробую синтаксис:
with gui.vertical:
text = gui.label('hello!')
items = gui.selection(['one', 'two', 'three'])
@gui.button('click me!')
def button():
text.value = items.value
foreground = red
(идея состоит в том, что аналогично тому, как метка состоит из текста, кнопка состоит из текста и функции)