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

Jp Calderone exarkun at divmod.com
Tue Aug 3 07:36:19 CEST 2004


Guido van Rossum wrote:

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): Ah, yuck. Not an acceptable solution. And it doesn't let you write [classmethod] -- you have to wrap the 'classmethod' in something relatively ugly.

 Here's a brief test for a syntax-change-less implementation of this 

feature, not as complete as test_decorators, but a good start, I believe:

def test(): try: from test import test_decorators except ImportError: test_decorators = None

 class DecoratorTest(object):
     __metaclass__ = DecoratableType

     def foo(self):
         print 'undecorated foo'

     decorate(staticmethod)
     def bar(x):
         print x

     decorate(classmethod)
     def baz(cls, y):
         print cls, y

     if test_decorators:
         counts = {}
         decorate(test_decorators.countcalls(counts),
                  test_decorators.memoize)
         def quux(self):
             print 'quux called'

 o = DecoratorTest()
 o.foo()
 o.bar('static method on instance object')
 o.baz('class method on the instance')
 DecoratorTest.bar('static method on the class')
 DecoratorTest.baz('class method on the class')

 if test_decorators:
     print 'Calling quux once'
     o.quux()
     print 'Calling quux twice'
     o.quux()
     print 'Called quux', DecoratorTest.counts['quux'], 'times'

 And here's the implementation, without using settrace, and without 

requiring wrappers for individual decorators:

import inspect

MAGIC_NAME = 'internal_decorators_list'

class Decorator(object): def init(self, firstCallable, *callables): cf = inspect.currentframe() self.callables = [firstCallable] + list(callables) self.decoratesLine = cf.f_back.f_lineno + 1 cf.f_back.f_locals.setdefault(MAGIC_NAME, []).append(self)

 def __call__(self, f):
     i = iter(self.callables)
     f = i.next()(f)
     for c in i:
         f = c(f)
     return f

decorate = Decorator

class DecoratableType(type): def new(cls, name, bases, attrs): decorators = attrs.get(MAGIC_NAME, []) if decorators: del attrs[MAGIC_NAME] lines = {} for (k, v) in attrs.items(): try: source, lineno = inspect.getsourcelines(v) except: pass else: lines[lineno] = k for d in decorators: if d.decoratesLine in lines: k = lines[d.decoratesLine] attrs[k] = d(attrs[k]) return super(DecoratableType, cls).new( cls, name, bases, attrs)

 There are clear drawbacks to this approach.  Metaclass required, no 

obvious ways to support free functions, and it depends slightly hackishly on the inspect module. I think at least one of these problems can be solved (and line number handling can be made smarter to deal with intervening comments, whitespace, etc).

 What are the advantages?

I realize there is little or no chance of '@decorator' being pulled from 2.4a2. I hope that something along the lines of the above will be considered, instead, for the next alpha, unless there is widespread community support for '@decorator', as opposed to the ridiculously faint support ("it's better than nothing") currently behind it.

Jean-Paul Calderone



More information about the Python-Dev mailing list