[Python-Dev] 2.4a2, and @decorators (original) (raw)
Jp Calderone exarkun at divmod.com
Tue Aug 3 07:36:19 CEST 2004
- Previous message: [Python-Dev] 2.4a2, and @decorators
- Next message: [Python-Dev] Re: 2.4a2, and @decorators
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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
- Previous message: [Python-Dev] 2.4a2, and @decorators
- Next message: [Python-Dev] Re: 2.4a2, and @decorators
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]