Wether or not PEP 318 makes it to Python 2.4, you can experiment with an alternative decorator syntax in Python 2.3 by hacking with bytecodes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107  | import dis, new
def _pop_funclist(f):
    """_pop_funclist(f) -> list or None
    Evaluates and returns a list constant defined at the beginning
    of a function. If the function doesn't begin with a list,
    or the list refers to parameters or other locals, a None is returned.
    The returned list is removed from the function code.
    """
    op = dis.opmap.__getitem__
    i = 0
    co = f.func_code
    s = co.co_code
    stopcodes = [op('LOAD_FAST'), op('STORE_FAST'), op('STORE_NAME'),
                  op('POP_TOP'), op('JUMP_FORWARD')]
    while i < len(s):
        code = ord(s[i])
        i += 1
        if code >= dis.HAVE_ARGUMENT:
            i += 2
        if code in stopcodes:
            return
        if code == op('BUILD_LIST') and ord(s[i]) == op('POP_TOP'):
            i += 1
            break
    else:
        return
    varname = '__func_list__'
    names = co.co_names + (varname,)
    dict_code = co.co_code[:i-1] + ''.join(map(chr, [
        op('STORE_NAME'),
        list(names).index(varname), 0,        
        op('LOAD_CONST'),
        list(co.co_consts).index(None), 0,
        op('RETURN_VALUE'),
        ]))
    func_code = chr(op('JUMP_FORWARD')) \
                + chr(i-3) + chr(0) \
                + co.co_code[3:]
    list_co = new.code(0, 0, co.co_stacksize, 64, dict_code,
                       co.co_consts, names, co.co_varnames, co.co_filename,
                       co.co_name, co.co_firstlineno, co.co_lnotab)
    func_co = new.code(co.co_argcount, co.co_nlocals, co.co_stacksize, co.co_flags, func_code,
                       co.co_consts, co.co_names, co.co_varnames, co.co_filename,
                       co.co_name, co.co_firstlineno, co.co_lnotab)
    f.func_code = func_co
    globals = f.func_globals.copy()
    exec list_co in globals
    result = globals[varname]
    return result
    
def decorate(func):
    """decorate(func) -> func
    Gets the decorator list from a function and if found,
    applies the decorators in order and returns the transformed
    function.
    """    
    funclist = _pop_funclist(func)
    if funclist is None: return func
    f = func
    for d in funclist:
        if callable(d):
            f = d(f)
    return f
    
def attrs(**kw):
    def setattrs(f):
        f.__dict__.update(kw)
        return f
    return setattrs
class list_decorators(type):
    """
    Metaclass that calls the decorate function for all
    methods defined in the class.
    """
    def __init__(cls, name, bases, dct):
        type.__init__(cls, name, bases, dct)
        for k,v in dct.iteritems():
            if hasattr(v, "func_code"):
                setattr(cls, k, decorate(v))
    
class TestClass(object):
    __metaclass__ = list_decorators
    def normal(self):
        self.x = 10
    def static(x):
        [attrs(author="shang", version=2),
         staticmethod]
        print x
    def name(cls):
        [classmethod]
        return cls.__name__
>>> TestClass.static.author
'shang'
>>> TestClass.static.version
2
>>> TestClass.name()
'TestClass'
 | 
The _pop_funclist function looks for the list building bytecodes in the beginning of the function and executes them to generate the list. It then "removes" them from the function by adding a FORWARD_JUMP bytecode in the beginning of the function that skips the list initialization (this is done so that you can use slow functions and functions with side-effects in the decorator list).
Download
Copy to clipboard
This is interesting, but wasn't Guido's proposal to put it immediately before function definition, not inside the function body?
True, but it was a bit cleaner to implement it the other way around, since this way the list ends up with the function object when compiled. I'll see if I can implement Guido's actual proposal by going through the module level bytecodes.