[Python-Dev] 2.4a2, and @decorators

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?

   * 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


More information about the Python-Dev mailing list