Welcome, guest | Sign In | My Account | Store | Cart
3

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.

Python, 107 lines
  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).

2 comments

Bob Ippolito 10 years, 1 month ago  # | flag

This is interesting, but wasn't Guido's proposal to put it immediately before function definition, not inside the function body?

Sami Hangaslammi (author) 10 years, 1 month ago  # | flag

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.

Add a comment

Sign in to comment

Created by Sami Hangaslammi on Fri, 18 Jun 2004 (PSF)
Python recipes (4237)
Sami Hangaslammi's recipes (7)

Required Modules

  • (none specified)

Other Information and Tasks