Compile time evaluation (aka eliminating default argument hacks)

Nick Coghlan ncoghlan at iinet.net.au
Thu Feb 24 06:15:09 EST 2005


Time for another random syntax idea. . .

So, I was tinkering in the interactive interpreter, and came up with the 
following one-size-fits-most default argument hack:

Py> x = 1
Py> def _build_used():
...   y = x + 1
...   return x, y
...
Py> def f(_used = _build_used()):
...   x, y = _used
...   print x, y
...
Py> f()
1 2
Py> x = 3
Py> f()
1 2

Works pretty well in terms of getting early binding, compile-time evaluation of 
expensive values and psuedo-constants, initialising a local with a shadowed 
value from an outer scope, sharing mutable values between invocations, and well, 
really, anything that default argument hacks are used for. It has the benefit of 
allowing use of a full suite at compile time, instead of the expressions one is 
usually limited to in default arguments hacks. It also slightly improves the 
argspec pollution situation by only using one argument instead of several.

However, it's still ugly as sin, still pollutes the functions argspec, the lists 
of names in the assignment statement and the return statement have to be kept in 
sync manually, and you're now polluting the outer namespace as well. Not to 
mention the fact that the contents of the compile-time functions are miles away 
from where the results are used.

But consider a syntax like the following:

     def f():
         use x, y from:
             y = x + 1  # [1]
         print x, y

[1] I'll grant that the binding of x from the outer scope here is more than a 
little obscure. However, I could see 'use x from: pass' becoming an idiom for 
early binding, in which case the example 'use' block could be written:
     use y from: y = x + 1
     use x from: pass

Then mixing early binding with early evaluation in the same 'use' block might 
simply be considered bad style, and discouraged (although not prevented).

Essentially, the function is compiled as usual, and emits code at the location 
of the 'use' statement equivalent to that for "x, y = <const>". The relevant 
entry in co_consts is populated by executing the body of the 'use' statement 
with an implicit "return x, y" at the end. The environment for that execution is 
the same as that for any function defined at the same level as the containing 
scope of the 'use' statement (e.g. module level in the example).

Multiple 'use' blocks would be allowed in a scope. A 'use' block at module level 
would simply mean that the result of calling the block gets marshalled into the 
compiled module file, rather than the block itself.

You could get performance improvements on *any* function, simply by moving code 
which doesn't depend on the functions arguments inside a 'use' block. For 
modules, data structures initialised inside a using block could simply be 
unmarshalled rather than built anew.

Cheers,
Nick.

P.S. I didn't search the archive, because I couldn't figure out any search terms 
for the topic that weren't swamped by irrelevant hits.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net



More information about the Python-list mailing list