[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