[Python-Dev] Re: decorators and 2.4
Phillip J. Eby
pje at telecommunity.com
Fri Jun 25 01:27:03 EDT 2004
At 01:26 PM 6/24/04 -0400, David Abrahams wrote:
>Anthony Baxter <anthony at interlink.com.au> writes:
>
> > So, let the floodgates open. Remember, we _can_ change this any
> > time up until 2.4b1, if there's a decision that the chosen form
> > sucks. :-)
>
>Here's thinking in a different direction altogether:
>
> No special syntax
>
> Instead, expose enough functionality in standard library functions
> that an appropriately-written pure-python "decorate" function can
> do it.
>
>
> decorate(staticmethod, my_decorator)
> def f(x):
> whatever()
>
> Since function definitions are executable statements, it should in
> principle be possible to arrange something that allows one to hook
> the execution of that statement. Perhaps it's already doable with
> the debugger hook?
Hmmm. You probably *could* create such a function in CPython, maybe even
as far back as 2.1 (since all it needs is sys._getframe and the tracing
hook), but it wouldn't be portable to Jython.
And, boy, would it be a sick and twisted piece of code. You'd need to do
something like keep a copy of the locals and on each "new line" event from
the trace hook, you'd need to both call the old trace hook (so that
debugging could still take place) and check to see if the locals had a new
function object. As soon as the function object appeared, you could do
your dirty work (after first restoring the *old* value bound to that name,
so the decorators would have access to the previous binding of the name).
About the only bit you couldn't do in pure Python would be decorating a
function defined in a nested scope, because "fast locals" aren't accessible
from pure Python. But for module-level functions and methods in classes,
this might actually work. Heck, I'm rather tempted to try to write it and
see if it does. At worst, it might be a fun way to learn the ins and outs
of sys.settrace().
[goes away for 30 minutes or so]
Hm, interesting. After a bit of twiddling, I've got a rough draft of the
trace mechanism. It seems to be possible to chain tracer functions in the
manner described, and I got it to work even with pdb running. That is, if
the debugger was already tracing a frame, adding co-operative trace
functions didn't appear to disturb the debugger's operation.
However, there are certainly some "interesting" peculiarities in the trace
function behavior. There seems to be a bit of voodoo
necessary. Specifically, it seems as though one must call 'sys.settrace()'
*and* set 'frame.f_trace' in order to change a local frame trace function,
even though in theory just returning a new trace function from a 'line'
event should work.
Ah well. Here's a proof-of-concept version, that uses right-to-left
application and simple calling for the decorators, but as you'll see, the
bulk of the code is devoted to mechanism rather than policy. You'll see
that it's easy to write different policies, although the way I have the
trace mechanism set up, later tracers execute *before* earlier
tracers. I'm not sure if this is the right way to go, but it seems to read
best with this syntax.
Anyway, it works with CPython 2.2. Caveat: do *not* use listcomps or any
other assignment statements between your 'decorate()' invocation and the
function definition, or the decorators will be applied to the bound-to
variable!
Personally, I think the 'decorate()' function is probably actually not that
useful, compared to making specialized decoration functions like, say:
classmethod_follows()
def foo(klass, x, y, z):
# ...
Only with better names. :) Also, using this approach means you can do
something like:
[classmethod_follows()]
def foo(klass, x, y, z):
# ...
which comes awfully close to Guido's preferred syntax!
Anyway, concept code follows, and I'm quite tempted to add this to
PyProtocols, which already has a similar function for class decoration.
# =======
import sys
def add_assignment_advisor(callback,depth=2):
frame = sys._getframe(depth)
oldtrace = [frame.f_trace]
old_locals = frame.f_locals.copy()
def tracer(frm,event,arg):
if event=='call':
if oldtrace[0]:
return oldtrace[0](frm,event,arg)
else:
return None
try:
if frm is frame and event !='exception':
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:
return tracer
callback(frm,k,v)
finally:
if oldtrace[0]:
oldtrace[0] = oldtrace[0](frm,event,arg)
frm.f_trace = oldtrace[0]
sys.settrace(oldtrace[0])
return oldtrace[0]
frame.f_trace = tracer
sys.settrace(tracer)
def decorate(*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)
decorate(classmethod)
def f(klass,x,y):
pass
print f
More information about the Python-Dev
mailing list