2.5 bytecodehacks.macro

This module allows the writing of macros, which are (to be brief) procedures that can affect their calling environment. If I understand the terminology properly, these are semantic, rather than syntactic, macros. The Python interpreter sees the functions before macro expansion is performed, which means that this package cannot be used to invent new syntax for Python.

Lars Marius Garhsol said on comp.lang.python that

bytecodehacks, although certainly cool, are just a pale shadow of what CL [Commpn Lisp] macros provide.

and at least the second half of the sentence is true <wink/>.

An example may help. A common thread on comp.lang.python is the debate about whether assignment should be an expression or a statement.

People (not me; I'm generally happy with the status quo) want to write code like this:

def frobulate(file):
    while line = file.readline():
        frob(line)

The usual response on the newsgroup is to write this like so:

def frobulate(file):
    while 1:
        line = file.readline():
        if not line:
            break
        frob(line)

This deeply offends some people, so I offer them an alternative:

def frobulate(file):
    while setq(line, file.readline()):
        frob(line)
frobulate = setqize(frobulate)

setqize can be impemented like so:

from bytecodehacks.macro import expand_these

def _setq((x),v):
    x = v
    return v

def setqize(f):
    return expand_these(f,setq=_setq)
Note that setq looks very much like a regular function. It is in fact syntactically permissible Python, which is obvious because it is part of a Python source file. The extra parentheses around the "x" parameter are used to indicate the fact that this variable is ``imported'' from the calling environment, so that modifications to it are propagated outside the body of setq.

This package takes advantage of complex arguments to functions. Complex arguments look like this:

def comp((a,b),c):
    print a,b,c

Python generates code to automatically unpack the tuple passed as the first argument. I was slightly surprised to learn that the pointless construct

def pointless((a),c):
    print a,c

was syntactically valid. I was also surprised to learn that Python generates rather poor code for such cases. However this is a good thing, because it means I can tell when arguments are declared in this fashion and use thing information to tell when to ``import'' variables into a function's scope.

Arguments surrounded by parentheses are a bit like the call-by-reference parameters you may have come across in other languages. You must pass a variable directly to these arguments. There are just too many issues in working out what code like

setq(x+1,2*x)

should mean for there to be an alternative.

A side effect of all this is that macros cannot take complex arguments.

Another side effect that you should be aware of is that any variable imported into a macro is converted to a local variable throughout the calling function when the macro is expanded.

There are two functions that perform macro expansion, expand which can take a dictionary mapping macro names to macros, and expand_these which takes a set of macros as keyword arguments.

If no dictionary is passed to expand, then a global dictionary is used instead. This dictionary can be added to using the function macros.add_macro. The idea is that a collection of useful macros might be built up over time, and that it will be more convenient to expand them all at once, rather than having to remember which you've used in a call to expand_these.

add_macro ([name, ] macro)
Add macro macro to the global macro dictionary, under the name name. Defaults to "macro.func_name".

expand (func[, macros])
macros defaults to the global macro dictionary.

Expand any calls to macros named in macros in func.

expand_these (func, name1=macro1, name2=macro2...)
Expand any calls to macros named name1, name2, etc. in func with their associated expansions.

Send comments to mwh@python.net