查找在with: Block中定义的函数。

时间:2022-06-01 21:06:47

Here's some code from Richard Jones' Blog:

以下是理查德·琼斯博客中的一些代码:

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

My question is: how the heck did he do this? How can the context manager access the scope inside the with block? Here's a basic template for trying to figure this out:

我的问题是:他到底是怎么做到的?上下文管理器如何访问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 个解决方案

#1


12  

Here's one way:

这里有一个方法:

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()

Edit: stretched code a bit more, added some explanation...:

编辑:扩展代码,添加一些解释…

Catching caller's locals at __exit__ is easy -- trickier is avoiding those locals that were already defined before the with block, which is why I added to main two local functions that the with should ignore. I'm not 100% happy with this solution, which looks a bit complicated, but I couldn't get equality testing correct with either == or is, so I resorted to this rather complicated approach.

在__exit__中捕获调用者的本地变量很容易——更麻烦的是避免那些在with块之前已经定义的本地变量,这就是为什么我在主函数中添加了两个必须忽略的本地函数。我对这个解决方案不是100%满意,它看起来有点复杂,但是我不能用==或is得到正确的等式测试,所以我采用了这种相当复杂的方法。

I've also added a loop (to make more strongly sure the defs before / within / after are being properly handled) and a type-check and function-call to make sure the right incarnation of testing is the one that's identified (everything seems to work fine) -- of course the code as written only works if the def inside the with is for a function callable without arguments, it's not hard to get the signature with inspect to ward against that (but since I'm doing the call only for the purpose of checking that the right function objects are identified, I didn't bother about this last refinement;-).

我还添加了一个循环(更强烈确定def正在妥善处理前/中/后)和类型检查函数调用,以确保正确的测试是化身的标识(一切都似乎工作好)——当然,编写的代码只适用与内部的def是可调用的函数没有参数,要得到带有inspect的签名以防止这种情况发生并不困难(但是由于我只是为了检查正确的函数对象是否被标识,所以我不需要考虑最后的改进;-)

#2


1  

To answer your question, yes, it's frame introspection.

回答你的问题,是的,这是框架自省。

But the syntax I would create to do the same thing is

但是我要创建的语法也是一样的

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

Here I would implement gui.button as a decorator that returns button instance given some parameters and events (though it appears to me now that button = gui.button('click me!', mybutton_onclick is fine as well).

这里我将实现gui。按钮作为一个decorator,在给定一些参数和事件的情况下返回按钮实例(尽管在我看来按钮= gui。”按钮(点击我!, mybutton_onclick也可以)。

I would also leave gui.vertical as it is since it can be implemented without introspection. I'm not sure about its implementation, but it may involve setting gui.direction = gui.VERTICAL so that gui.label() and others use it in computing their coordinates.

我也会离开gui。它是垂直的,因为无需自省就可以实现它。我不确定它的实现,但是它可能涉及到设置gui。方向= gui。垂直的,以便gui.label()和其他人在计算他们的坐标时使用它。

Now when I look at this, I think I'd try the syntax:

当我看到这个的时候,我想我应该试试它的语法:

    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

(the idea being that similarly to how label is made out of text, a button is made out of text and function)

(这个想法是,就像标签是如何由文本构成一样,按钮是由文本和函数构成的)

#1


12  

Here's one way:

这里有一个方法:

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()

Edit: stretched code a bit more, added some explanation...:

编辑:扩展代码,添加一些解释…

Catching caller's locals at __exit__ is easy -- trickier is avoiding those locals that were already defined before the with block, which is why I added to main two local functions that the with should ignore. I'm not 100% happy with this solution, which looks a bit complicated, but I couldn't get equality testing correct with either == or is, so I resorted to this rather complicated approach.

在__exit__中捕获调用者的本地变量很容易——更麻烦的是避免那些在with块之前已经定义的本地变量,这就是为什么我在主函数中添加了两个必须忽略的本地函数。我对这个解决方案不是100%满意,它看起来有点复杂,但是我不能用==或is得到正确的等式测试,所以我采用了这种相当复杂的方法。

I've also added a loop (to make more strongly sure the defs before / within / after are being properly handled) and a type-check and function-call to make sure the right incarnation of testing is the one that's identified (everything seems to work fine) -- of course the code as written only works if the def inside the with is for a function callable without arguments, it's not hard to get the signature with inspect to ward against that (but since I'm doing the call only for the purpose of checking that the right function objects are identified, I didn't bother about this last refinement;-).

我还添加了一个循环(更强烈确定def正在妥善处理前/中/后)和类型检查函数调用,以确保正确的测试是化身的标识(一切都似乎工作好)——当然,编写的代码只适用与内部的def是可调用的函数没有参数,要得到带有inspect的签名以防止这种情况发生并不困难(但是由于我只是为了检查正确的函数对象是否被标识,所以我不需要考虑最后的改进;-)

#2


1  

To answer your question, yes, it's frame introspection.

回答你的问题,是的,这是框架自省。

But the syntax I would create to do the same thing is

但是我要创建的语法也是一样的

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

Here I would implement gui.button as a decorator that returns button instance given some parameters and events (though it appears to me now that button = gui.button('click me!', mybutton_onclick is fine as well).

这里我将实现gui。按钮作为一个decorator,在给定一些参数和事件的情况下返回按钮实例(尽管在我看来按钮= gui。”按钮(点击我!, mybutton_onclick也可以)。

I would also leave gui.vertical as it is since it can be implemented without introspection. I'm not sure about its implementation, but it may involve setting gui.direction = gui.VERTICAL so that gui.label() and others use it in computing their coordinates.

我也会离开gui。它是垂直的,因为无需自省就可以实现它。我不确定它的实现,但是它可能涉及到设置gui。方向= gui。垂直的,以便gui.label()和其他人在计算他们的坐标时使用它。

Now when I look at this, I think I'd try the syntax:

当我看到这个的时候,我想我应该试试它的语法:

    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

(the idea being that similarly to how label is made out of text, a button is made out of text and function)

(这个想法是,就像标签是如何由文本构成一样,按钮是由文本和函数构成的)