Recipe 19.4. Unpacking a Few Items in a Multiple Assignment
Credit: Brett Cannon, Oren Tirosh, Alex Martelli
Problem
Python's multiple
unpacking assignment is very handy when you are unpacking all the
items from a sequence and binding each to a name. However, you often
need to unpack (and bind to names) only some items from the
"front" of a sequence, and bind
another name to "the rest" of the
sequence (the part you didn't unpack).
Solution
A generator provides an elegant solution to this problem:
def peel(iterable, arg_cnt=1):
"" Yield each of the first arg_cnt items of the iterable, then
finally an iterator for the rest of the iterable. ""
iterator = iter(iterable)
for num in xrange(arg_cnt):
yield iterator.next( )
yield iterator
if _ _name_ _ == '_ _main_ _':
t5 = range(1, 6)
a, b, c = peel(t5, 2)
print a, b, list(c)
# emits: 1 2 [3, 4, 5]
Discussion
Python supports the handy idea of multiple unpacking assignment. Say
that t5 is any sequence of five items.
Then, you can code:
a, b, c, d, e = t5to bind a name to each item of t5.However, you often do not know (nor care) exactly how many items a
certain sequence t holds: you want to bind
(say) two names, one to each of the first two items, and a third name
to "the rest" of
t (this requirement does imply that t must
hold at least two items). If you know that
t is a
"proper" sequence, with support for
slicing, not just an arbitrary iterable, you can code:
a, b = t[:2]but this is nowhere as elegant or handy as the multiple unpacking
c = t[2:]
assignment. Moreover, if you are not certain about the nature of
t (i.e., if t
can be any iterable, not necessarily supporting slice syntax), the
task becomes more cumbersome:
c = iter(t5)Given these issues, the Python Development mailing list[1] once discussed a new
a = c.next( )
b = c.next( )
syntax for generalized multiple unpacking assignment, such that:
[1] The Python Development mailing list is the list on which all
discussion regarding the development of Python itself is held; see
http://mail.python.org/pipermail/python-dev/2002-November/030380l
for this specific subject.
a, b, *c = twould perform exactly this taskbind names
a and b to the
first two items of t and name
c to "the
rest".I didn't like the idea of making the Python language
bigger by adding this extra functionality to assignment statements,
so I came up with this recipe's generator. This
generator provides this functionality fully and without any need to
add any new syntax to Python.Just one caveat: you must make sure that you pass the
arg_cnt argument properly. If you pass a wrong value
for arg_cnt, or if the sequence you pass to
peel is shorter than arg_cnt, you
get an exception at runtime. But then, you also get a Python
exception at runtime if you try to perform a multiple assignment and
the number of names you have on the left of the =
sign is not identical to the number of items of the sequence you have
on the right. Therefore, this recipe isn't any
different from normal, multiple unpacking assignment in this respect.
If you think it is important to relax some parts of this requirement,
see Recipe 19.5.
See Also
Language Reference and Python in a
Nutshell about multiple unpacking assignments; Recipe 19.5.