在with块中查找定义的函数

11 投票
2 回答
1163 浏览
提问于 2025-04-15 13:30

这里有一些来自 Richard Jones 博客 的代码:

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 个回答

2

来回答你的问题,是的,这就是框架的自我检查。

不过,我会用这样的语法来实现同样的功能:

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

(这个想法是,类似于标签是由文本构成的,按钮也是由文本和功能构成的)

14

这里有一种方法:

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应该忽略它们。我对这个解决方案不是特别满意,因为看起来有点复杂,但我用==is进行相等性测试时总是出错,所以我只好采用这个相对复杂的方法。

我还添加了一个循环(以更强烈地确保def在前面/里面/后面都被正确处理)以及一个类型检查和函数调用,以确保识别到的是正确的testing版本(看起来一切都正常)——当然,按这样写的代码只适用于with内部的def是一个不带参数的函数,获取签名用inspect来防止这个问题并不难(但由于我只是为了检查正确的函数对象,所以没有去做这个最后的细化;-)。

撰写回答