[Python-Dev] 2.4a2, and @decorators (original) (raw)

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