Recipe 4.12. Building a Dict from a List of Alternating Keys and Values
Credit: Richard Philips, Raymond Hettinger
Problem
You want to build a dict from a list of alternating keys and values.
Solution
The built-in type dict offers many ways to build
dictionaries, but not this one, so we need to code a function for the
purpose. One way is to use the built-in function
zip on extended slices:
def dictFromList(keysAndValues):A more general approach, which works for any sequence or other
return dict(zip(keysAndValues[::2], keysAndValues[1::2]))
iterable argument and not just for lists, is to
"factor out" the task of getting a
sequence of pairs from a flat sequence into a separate generator.
This approach is not quite as concise as
dictFromList, but it's faster as
well as more general:
def pairwise(iterable):Defining pairwise also allows
itnext = iter(iterable).next
while True:
yield itnext( ), itnext( )
def dictFromSequence(seq):
return dict(pairwise(seq))
updating an existing dictionary with any
sequence of alternating keys and valuesjust code, for example,
mydict.update(pairwise(seq)).
Discussion
Both of the "factory functions" in
this recipe use the same underlying way to construct a dictionary:
each calls dict with an argument that is a
sequence of (key, value) pairs. All the difference
is in how the functions build the sequence of pairs to pass to
dict.dictFromList builds a list of such pairs by calling
built-in function zip with two extended-form
slices of the function's
keysAndValues argumentone that gathers all
items with even indices (meaning the items at index 0, 2, 4, . . .),
the other that gathers all items with odd indices (starting at 1 and
counting by 2 . . .). This approach is fine, but it works only when
the argument named keysAndValues is an instance of a
type or class that supports extended slicing, such as
list, tuple or
str. Also, this approach results in constructing
several temporary lists in memory: if keysAndValues
is a long sequence, all of this list construction activity can cost
some performance.dictFromSequence, on the other hand, delegates the
task of building the sequence of pairs to the generator named
pairwise. In turn, pairwise is
coded to ensure that it can use any iterable at allnot just
lists (or other sequences, such as tuples or strings), but also, for
example, results of other generators, files, dictionaries, and so on.
Moreover, pairwise yields pairs one at a time. It
never constructs any long list in memory, an aspect that may improve
performance if the input sequence is very long.The implementation of pairwise is interesting. As
its very first statement, pairwise binds local name
itnext to the bound-method next
of the iterator that it obtains by calling the built-in function
iter on the iterable argument.
This may seem a bit strange, but it's a good general
technique in Python: if you start with an object, and all you need to
do with that object is call one of its methods in a loop, you can
extract the bound-method, assign it to a local name, and afterwards
just call the local name as if it were a function.
pairwise would work just as well if the
next method was instead called in a way that may
look more normal to programmers who are used to other languages:
def pairwise_slow(iterable):However, this pairwise_slow variant
it = iter(iterable)
while True:
yield it.next( ), it.next( )
isn't really any simpler than the
pairwise generator shown in the Solution
("more familiar to people who don't
know Python" is not a synonym
of "simpler"!), and it
is about 60% slower. Focusing on simplicity and
clarity is one thing, and a very good oneindeed, a core
principle of Python. Throwing performance to the winds,
without getting any real advantage to
compensate, is a completely different proposition and definitely not
a practice that can be recommended in any language. So, while it is
an excellent idea to focus on writing correct, clear, and simple
code, it's also very advisable to learn and use
Python's idioms that are most appropriate to your
needs.
See Also
Recipe 19.7 for more
general approaches to looping by sliding windows over an iterable.
See the Python Reference Manual for more on
extended slicing.