
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? * It works with Python 2.2 and Python 2.3. * It requires no interpreter changes. It can be distributed with the standard library, as a cookbook recipe, or in the official documentation as a hint regarding more "advanced" decorator usage. * It introduces no new syntax and uses up no operator character. * It supports arbitrary expressions. * It's pure python. 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