Поиск функций, определенных в with: Block - PullRequest
9 голосов
/ 10 августа 2009

Вот код из блога Ричарда Джонса :

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

Ответы [ 2 ]

12 голосов
/ 10 августа 2009

Вот один из способов:

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, поэтому я добавил к двум основным локальным функциям, что with следует игнорировать. Я не на 100% доволен этим решением, которое выглядит немного сложным, но я не смог получить правильное тестирование на равенство с помощью == или is, поэтому я прибег к такому довольно сложному подходу.

Я также добавил цикл (чтобы более точно убедиться, что def s до / в / после правильно обрабатываются) и проверку типов и вызов функции, чтобы убедиться в правильном воплощении testing это тот, который идентифицирован (кажется, что все работает нормально) - конечно, написанный код работает, только если def внутри with предназначен для функции, вызываемой без аргументов, получить подпись с * 1021 несложно * чтобы противостоять этому (но так как я делаю вызов только с целью проверки идентификации правильных функциональных объектов, я не стал беспокоиться об этом последнем уточнении; -).

1 голос
/ 10 августа 2009

Чтобы ответить на ваш вопрос, да, это самоанализ фрейма.

Но синтаксис, который я бы создал, чтобы сделать то же самое,

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

(идея заключается в том, что аналогично тому, как метка состоит из текста, кнопка состоит из текста и функции)

...