Neal said:
How can I write code to take advantage of new decorator syntax, while
allowing backward compatibility?
I almost want a preprocessor.
#if PYTHON_VERSION >= 2.4
@staticmethod
...
Since python < 2.4 will just choke on @staticmethod, how can I do this?
Here's one way to do it. The restrictions are:
it only works on modules, so you can't use decorators in the
main script;
it only handles one decorate per function (but shouldn't be
too hard to extend);
decorators are assumed to only occupy a single line.
Example of use:
----- example.py --------
import decorate23
import test
test.C().fred('it', 'works')
----- end example.py ----
----- test.py -----------
class C:
@staticmethod
def fred(a, b):
print a, b
return None
@classmethod
def freda(cls, a, b):
print a, b
return None
----- end test.py -------
The contents of decorate23 are, of course, left as an exercise
for the reader.
Only kidding:
----- decorate23.py -----
# Automatically apply decorators for pre-2.4 code
import sys
import re
if sys.version_info < (2,4):
from imputil import _compile, _suffix_char
import imp, sys, marshal, struct, __builtin__
import imputil
PATTERN = re.compile('^(?P<indent>[\\s]*)@(?P<decorator>.*)\n'
'(?P<body>\\1def (?P<id>[a-zA-Z_0-9]+)\\(.*(?:\n\\1.*)*)',
re.MULTILINE)
def autodecorate(source):
def replace(match):
decorator = match.group('decorator')
indent = match.group('indent')
id = match.group('id')
body = match.group('body')
return "%(body)s\n%(indent)s%(id)s = %(decorator)s(%(id)s)\n" % locals()
return PATTERN.sub(replace, source)
def hook_compile(pathname, timestamp):
"""Compile (and cache) a Python source file.
The file specified by <pathname> is compiled to a code object and
returned.
Presuming the appropriate privileges exist, the bytecodes will be
saved back to the filesystem for future imports. The source file's
modification timestamp must be provided as a Long value.
"""
codestring = open(pathname, 'rU').read()
if codestring and codestring[-1] != '\n':
codestring = codestring + '\n'
codestring = autodecorate(codestring)
code = __builtin__.compile(codestring, pathname, 'exec')
# try to cache the compiled code
try:
f = open(pathname + _suffix_char, 'wb')
except IOError:
pass
else:
f.write('\0\0\0\0')
f.write(struct.pack('<I', timestamp))
marshal.dump(code, f)
f.flush()
f.seek(0, 0)
f.write(imp.get_magic())
f.close()
return code
imputil._compile = hook_compile
imputil.ImportManager().install()
----- end decorate23.py -
The overhead shouldn't be too bad as the hook will only kick in when
the code needs recompiling, but the line numbers in tracebacks
refer to the modified code so they'll be slightly out.