If I'm understanding correctly what you want, it is theoretically impossible; the transformation that you seem to be describing is one that would have different effects on equivalent functions, depending on superficial details of their source-code that likely won't be preserved in the compiled form. For example, consider the following two versions of a given function:
def a (n, acc):
print('called a(%d,%d)' % (n, acc))
if n == 0: return acc
return a (n - 1, acc + n)
def a (n, acc):
print('called a(%d,%d)' % (n, acc))
if n == 0: return acc
ret = a (n - 1, acc + n)
return ret
Clearly they are functionally identical. In the source code, the only difference is that the former uses return
directly on a certain expression, whereas the latter saves the result of that expression into a local variable and then uses return
on that variable. In the compiled form, there need be no difference at all.
Now consider the "patched" versions:
def a (n, acc):
print('called a(%d,%d)' % (n, acc))
if n == 0: return acc
return lambda: a (n - 1, acc + n)
def a (n, acc):
print('called a(%d,%d)' % (n, acc))
if n == 0: return acc
ret = a (n - 1, acc + n)
return lambda: ret
Clearly these are very different: for example, if n
is 3
and acc
is 0, then the former prints called a(3,0)
and returns a function that prints called a(2,3)
and returns a function that prints called a(1,5)
and returns a function that prints called a(0,6)
and returns 6
, whereas the latter prints called a(3,0)
and called a(2,3)
and called a(1,5)
and called a(0,6)
and returns a function that returns a function that returns a function that returns 6
.
The broader difference is that the first "patched" function performs one step of the computation each time the new return-value is called, whereas the second "patched" version performs all steps of the computation during the initial call, and simply arranges a series of subsequent calls for the sake of entertainment. This difference will matter whenever there's a side-effect (such as printing a message, or such as recursing so deeply that you overflow the stack). It can also matter if the caller introduces a side-effect: note that these functions will only be recursive until some other bit of code redefines a
, at which point there is a difference between the version that plans to continue re-calling a
and the version that has already completed all of its calls.
Since you can't distinguish the two "unpatched" versions, you obviously can't generate the distinct "patched" versions that your transformation implies.