@staticmethod, backward compatibility?

Duncan Booth duncan.booth at invalid.invalid
Tue Sep 27 14:22:55 CEST 2005

Neal Becker wrote:

> 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:
    def fred(a, b):
        print a, b
        return None

    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.*)*)',
    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

        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
            f = open(pathname + _suffix_char, 'wb')
        except IOError:
            f.write(struct.pack('<I', timestamp))
            marshal.dump(code, f)
            f.seek(0, 0)

        return code

    imputil._compile = hook_compile
----- 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.

More information about the Python-list mailing list