
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.