Finding Functions Defined in a with: Block

contextmanager, python, scope, with-statement

Solution

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.

I've also added a loop (to make more strongly sure the `def`s 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;-).

Problem

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: ``` 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 ```

Original source