Recipe 19.5. Automatically Unpacking the Needed Number of Items
Credit: Sami Hangaslammi, Peter Cogolo
Problem
You want to unpack (and bind to names) some items from the
"front" of a sequence and bind
another name to "the rest" of the
sequence (the part you didn't unpack). You want to
obtain the number of items to unpack automatically, based on how many
names are on the left of the = sign in a multiple
unpacking assignment.
Solution
The previous approach in Recipe 19.4 is clean and elegant, but
you have to "manually" pass the
number of items to unpack. If you're willing to
stoop to a little black magic, peering into stack frames and
bytecodes, you may be able to bypass that requirement:
import inspect, opcode
def how_many_unpacked( ):
f = inspect.currentframe( ).f_back.f_back
if ord(f.f_code.co_code[f.f_lasti]) == opcode.opmap['UNPACK_SEQUENCE']:
return ord(f.f_code.co_code[f.f_lasti+1])
raise ValueError, "Must be a generator on RHS of a multiple assignment!"
def unpack(iterable):
iterator = iter(iterable)
for num in xrange(how_many_unpacked( )-1):
yield iterator.next( )
yield iterator
if _ _name_ _ == '_ _main_ _':
t5 = range(1, 6)
a, b, c = unpack(t5)
print a, b, list(c)
Discussion
While arguably spiffy, this recipe is a bit fragile, as you could
well expect from a function relying on introspection on bytecode:
while the recipe works in Python 2.3 and 2.4, any future release of
Python might easily generate bytecode for a multiple unpacking
assignment in a somewhat different way, and thus break the recipe.Moreover, as presented, the recipe relies on
how_many_unpacked being called specifically from a
generator; if you call it from an ordinary function, it does not
work, since in that case the UNPACK_SEQUENCE
bytecode in the caller's caller happens to fall at
offset f.f_lasti+3 instead of
f.f_lasti.For example, the following code doesn't work with
the recipe's Solution because
enumfunc is an ordinary function, not a generator:
def enumfunc( ):However, the following code does work:
return xrange(how_many_unpacked( ))
a, b, c, d, e = enumfunc( )
def enumgen( ):because enumgen is a generator.In other words, this recipe is a hackarguably a
for x in xrange(how_many_unpacked( )): yield x
a, b, c, d, e = enumgen( )
neat hack (to the point that one of the editors
of this Cookbook successfully lobbied the
"other" two and managed to obtain
the recipe's inclusion in this volume), but,
nevertheless, a hack. Therefore, you probably do not want to
use this approach in
"production code", meaning code
that must stay around for a long time and will be maintained across
future versions of Python.Nevertheless, you could make
how_many_unpacked work in both contexts by making it
a little bit more complicated:
def how_many_unpacked( ):With this more complicated variant,
f = inspect.currentframe( ).f_back.f_back
bytecode = f.f_code.co_code
ups_code = opcode.opmap['UNPACK_SEQUENCE']
if ord(bytecode[f.f_lasti]) == ups_code:
return ord(bytecode[f.f_lasti+1])
elif ord(bytecode[f.f_lasti+3]) == ups_code:
return ord(bytecode[f.f_lasti+4])
else:
raise ValueError, "Must be on the RHS of a multiple assignment!"
how_many_unpacked would work when called from either
a generator or an ordinary function. However, I recommend sticking
with the simpler version presented in this recipe's
Solution, and calling how_many_unpacked only from
the given unpack generator, or a few other specific
generators.Even such a limited use can be considered debatable, since most
Pythonistas prefer clarity and simplicity to the risky kind of
"convenience" that can be obtained
by such shortcuts. After all, this recipe's only
advantage, in comparison to Recipe 19.4, is that you save yourself
the trouble of passing to unpack the number of items
required, which is nice, but clearly, not all that
crucial."
See Also
Recipe 19.4;
Language Reference and Python in a
Nutshell about multiple unpacking assignments;
Library Reference and Python in a
Nutshell about library modules inspect
and opcode.