[Python-Dev] 2.4a2, and @decorators

Phillip J. Eby pje at telecommunity.com
Mon Aug 2 21:49:41 CEST 2004


At 12:09 PM 8/2/04 -0700, Guido van Rossum wrote:
> > >>I would think the fact that the '[decorators]' syntax can be implemented
> > >>in pure Python (no changes to the interpreter) for existing Python
> > >>versions would give more weight to it.
>
>Can it?  I must've missed that.  It sure sounds like an incredible
>hack -- how to you prevent the default behavior that the list of
>decorators is thrown away by the interpreter?

By using sys.settrace (and a careful tracer implementation to avoid 
interfering with debuggers or other active tracers):

def as(*decorators):

     if len(decorators)>1:
         decorators = list(decorators)
         decorators.reverse()

     def callback(frame,k,v):
         for d in decorators:
             v = d(v)
         frame.f_locals[k] = v

     add_assignment_advisor(callback)


def add_assignment_advisor(callback,depth=2):
     """Invoke 'callback(frame,name,value)' on the next assignment in 'frame'

     The frame monitored is determined by the 'depth' argument, which gets
     passed to 'sys._getframe()'.  Note that when 'callback' is invoked, the
     frame state will be as though the assignment hasn't happened yet, so any
     previous value of the assigned variable will be available in the frame's
     locals.  (Unless there's no previous value, in which case there will be
     no such variable in the frame locals.)
     """

     frame = sys._getframe(depth)
     oldtrace = [frame.f_trace]
     old_locals = frame.f_locals.copy()

     def tracer(frm,event,arg):
         if event=='call':
             # We don't want to trace into any calls
             if oldtrace[0]:
                 # ...but give the previous tracer a chance to, if it wants
                 return oldtrace[0](frm,event,arg)
             else:
                 return None

         try:
             if frm is frame and event !='exception':
                 # Aha, time to check for an assignment...
                 for k,v in frm.f_locals.items():
                     if k not in old_locals:
                         del frm.f_locals[k]
                         break
                     elif old_locals[k] is not v:
                         frm.f_locals[k] = old_locals[k]
                         break
                 else:
                     # No luck, keep tracing
                     return tracer

                 # Got it, fire the callback, then get the heck outta here...
                 callback(frm,k,v)

         finally:
             # Give the previous tracer a chance to run before we return
             if oldtrace[0]:
                 # And allow it to replace our idea of the "previous" tracer
                 oldtrace[0] = oldtrace[0](frm,event,arg)

         # Unlink ourselves from the trace chain.
         frm.f_trace = oldtrace[0]
         sys.settrace(oldtrace[0])
         return oldtrace[0]

     # Install the trace function
     frame.f_trace = tracer
     sys.settrace(tracer)



class X(object):
      [as(classmethod)]
      def foo(cls):
          ....


Of course, 'as' is a hack to let you use arbitrary callables as decorators, 
rather than having to use add_assignment_advisor directly.



> > >>That is, if someone wants to implement a decorator that's forwards
> > >>and backwards-compatible, that's possible with the list syntax,
> > >>but not the @ syntax.
> > >
> > >.. but that also means you can still make the [decorators] syntax
> > >work in 2.4, if you want compatibility or don't like @syntax.
> >
> > But then why not just make that the default syntax, so that no
> > migration is necessary, and only one syntax has to be
> > learned/explained to people?
>
>Because that syntax received significant boohs when I presented it at
>EuroPython.

And "@" *didn't*???  Ah well.  C'est la vie.



More information about the Python-Dev mailing list