De-index Arrays¶
Convert literal indexing operations for a given array into named value references. The new value names are de-indexed and stashed in the function’s closure so that the resulting code both uses no literal indices and still behaves as if it did. Variable indices are unaffected.
For example:
v = [object(), object(), object()]
@pragma.deindex(v, 'v')
def f(x):
yield v[0]
yield v[x]
# ... f becomes ...
def f(x):
yield v_0 # This is defined as v_0 = v[0] by the function's closure
yield v[x]
# We can check that this works correctly
assert list(f(2)) == [v[0], v[2]]
This can be easily stacked with pragma.unroll()
to unroll iterables in a function when their values are known at function definition time:
funcs = [lambda x: x, lambda x: x ** 2, lambda x: x ** 3]
@pragma.deindex(funcs, 'funcs')
@pragma.unroll(lf=len(funcs))
def run_func(i, x):
for j in range(lf):
if i == j:
return funcs[j](x)
# ... Becomes ...
def run_func(i, x):
if i == 0:
return funcs_0(x)
if i == 1:
return funcs_1(x)
if i == 2:
return funcs_2(x)
This could be used, for example, in a case where dynamically calling functions isn’t supported, such as in numba.jit
or numba.cuda.jit
.
Note that because the array being de-indexed is passed to the decorator, the value of the constant-defined variables (e.g. v_0
in the code above) is “compiled” into the code of the function, and won’t update if the array is updated. Again, variable-indexed calls remain unaffected.
Since names are (and must) be used as references to the array being de-indexed, it’s worth noting that any other local variable of the format "{iterable_name}_{i}"
will get shadowed by this function. The string passed to iterable_name
must be the name used for the iterable within the wrapped function.