This interesting unpacking function allows unpacking a nested tuple into the arguments of a function as its arguments, with structure checking support and only one function call overhead excluding one-time compilation cost.
def flatten_tuple(data, structure, cache={}):
if not (unpacker := cache.get(structure)):
def get_unstruct(structure, n_vars):
if isinstance(structure, int):
if structure == 0:
return "*x%d" % n_vars, n_vars + 1
if structure == -1:
return "_", n_vars
new_n_vars = n_vars + structure
ret = ",".join("x%d" % i for i in range(n_vars, new_n_vars))
n_vars = new_n_vars
return ret, new_n_vars
assert isinstance(structure, tuple)
unstruct_code = "("
for x in structure:
unstruct_code_one, n_vars = get_unstruct(x, n_vars)
unstruct_code += unstruct_code_one + ","
return unstruct_code + ")", n_vars
unstruct_code, n_vars = get_unstruct(structure, 0)
local_dict = {}
exec(
compile("def unpacker(data):\n %s = data\n return x%s" %
(unstruct_code, ",x".join(map(str, range(n_vars)))),
"",
"exec",
dont_inherit=True), {}, local_dict)
unpacker = local_dict["unpacker"]
cache[structure] = unpacker
return unpacker(data)
Usage example:
def f(x, y, z):
print(x, y, z)
f(*flatten_tuple((10, (20, 30)), (1, (1, 1))))
# prints: 10 20 30
Any thoughts on how it can be improved?
Edit:
Here's a quick performance comparison against flat
as given by @Reinderien:
def flat(t):
if isinstance(t, tuple):
for x in t:
yield from flat(x)
else:
yield t
In [2]: def f(x0,x1,x2,x3,x4,x5,x6,x7):
...: return x0+x1+x2+x3+x4+x5+x6+x7
...:
In [3]: %timeit f(*flat(((10,20,30),(40,50),(60,70,80))))
2.06 µs ± 69.6 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [4]: %timeit f(*flatten_tuple(((10,20,30),(40,50),(60,70,80)), ((3,),(2,),(3,))))
383 ns ± 17.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
def flat(t): if isinstance(t, tuple): for x in t: yield from flat(x) else: yield t
? \$\endgroup\$