I don't recommend using this, but just for fun here's some code that actually does what you want. When you call unpack(<sequence>)
, the unpack
function uses the inspect
module to find the actual line of source where the function was called, then uses the ast
module to parse that line and count the number of variables being unpacked.
Caveats:
- For multiple assignment (e.g.
(a,b) = c = unpack([1,2,3])
), it only uses the first term in the assignment
- It won't work if it can't find the source code (e.g. because you're calling it from the repl)
- It won't work if the assignment statement spans multiple lines
Code:
import inspect, ast
from itertools import islice, chain, cycle
def iter_n(iterator, n, default=None):
return islice(chain(iterator, cycle([default])), n)
def unpack(sequence, default=None):
stack = inspect.stack()
try:
frame = stack[1][0]
source = inspect.getsource(inspect.getmodule(frame)).splitlines()
line = source[frame.f_lineno-1].strip()
try:
tree = ast.parse(line, 'whatever', 'exec')
except SyntaxError:
return tuple(sequence)
exp = tree.body[0]
if not isinstance(exp, ast.Assign):
return tuple(sequence)
exp = exp.targets[0]
if not isinstance(exp, ast.Tuple):
return tuple(sequence)
n_items = len(exp.elts)
return tuple(iter_n(sequence, n_items, default))
finally:
del stack
# Examples
if __name__ == '__main__':
# Extra items are discarded
x, y = unpack([1,2,3,4,5])
assert (x,y) == (1,2)
# Missing items become None
x, y, z = unpack([9])
assert (x, y, z) == (9, None, None)
# Or the default you provide
x, y, z = unpack([1], 'foo')
assert (x, y, z) == (1, 'foo', 'foo')
# unpack() is equivalent to tuple() if it's not part of an assignment
assert unpack('abc') == ('a', 'b', 'c')
# Or if it's part of an assignment that isn't sequence-unpacking
x = unpack([1,2,3])
assert x == (1,2,3)
# Add a comma to force tuple assignment:
x, = unpack([1,2,3])
assert x == 1
# unpack only uses the first assignment target
# So in this case, unpack('foobar') returns tuple('foo')
(x, y, z) = t = unpack('foobar')
assert (x, y, z) == t == ('f', 'o', 'o')
# But in this case, it returns tuple('foobar')
try:
t = (x, y, z) = unpack('foobar')
except ValueError as e:
assert str(e) == 'too many values to unpack'
else:
raise Exception("That should have failed.")
# Also, it won't work if the call spans multiple lines, because it only
# inspects the actual line where the call happens:
try:
(x, y, z) = unpack([
1, 2, 3, 4])
except ValueError as e:
assert str(e) == 'too many values to unpack'
else:
raise Exception("That should have failed.")