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.