One-line expression to map dictionary to another
Let's take the excellent code from @karlknechtel and see what it does:
>>> d = dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
But how does it work?
To build a dictionary, you can use the dict()
function. It expects a list of tuples. In 3.x and >2.7, you can also use dictionary comprehension (see answer by @nightcracker).
Let's dissect the argument of dict. At first, we need a list of all items in m. Every item is a tuple in the format (key, value).
>>> d.items()
[('group_id', 3), ('user_id', 1), ('user', 'user1'), ('group_name', 'ordinary users')]
Given a key value k
, we could get the right key value from m
by doing m[k]
.
>>> k = 'user_id'
>>> m[k]
'uid'
Unfortunately, not all keys in d
also exist in m
.
>>> k = 'user'
>>> m[k]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'user'
To work around that, you can use d.get(x, y)
, which returns d[x]
if the key x
exists, or the default value y
if it doesn't. Now, if a key k
from d
doesn't exist in m
, we just keep it, so the default is k
.
>>> m.get(k, k).
'user'
Now we are ready to build a list of tuples to supply to dict()
. To build a list in one line, we can use list comprehension.
To build a list of squares, you would write this:
>>> [x**2 for x in range(5)]
[0, 1, 4, 9, 16]
In our case, it looks like this:
>>> [(m.get(k, k), v) for (k, v) in d.items()]
[('gid', 3), ('uid', 1), ('user', 'user1'), ('group', 'ordinary users')]
That's a mouthful, let's look at that again.
Give me a list [...]
, which consists of tuples:
[(.., ..) ...]
I want one tuple for every item x
in d
:
[(.., ..) for x in d.items()]
We know that every item is a tuple with two components, so we can expand it to two variables k
and v
.
[(.., ..) for (k, v) in d.items()]
Every tuple should have the right key from m as first component, or k if k doesn't exist in m, and the value from d.
[(m.get(k, k), v) for (k, v) in d.items()]
We can pass it as argument to dict()
.
>>> dict([(m.get(k, k), v) for (k, v) in d.items()])
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
Looks good! But wait, you might say, @karlknechtel didn't use square brackets.
Right, he didn't use a list comprehension, but a generator expression. Simply speaking, the difference is that a list comprehension builds the whole list in memory, while a generator expression calculates on item at a time. If a list on serves as an intermediate result, it's usually a good idea to use a generator expression. In this example, it doesn't really make a difference, but it's a good habit to get used to.
The equivalent generator expressions looks like this:
>>> ((m.get(k, k), v) for (k, v) in d.items())
<generator object <genexpr> at 0x1004b61e0>
If you pass a generator expression as argument to a function, you can usually omit the outer parentheses. Finally, we get:
>>> dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
There happens quite a lot in one line of code. Some say this is unreadable, but once you are used to it, stretching this code over several lines seems unreadable. Just don't overdo it. List comprehension and generator expressions are very powerful, but with great power comes great responsibility. +1 for a good question!
In 3.x:
d = {m.get(key, key):value for key, value in d.items()}
It works by creating a new dictionary which contains every value from d
and mapped to a new key. The key is retrieved like this: m[key] if m in key else key
, but then with the default .get function (which supports default values if the key doesn't exist).
Why would you want to do it in one line?
result = {}
for k, v in d.iteritems():
result[m.get(k, k)] = v
Sure:
d = dict((m.get(k, k), v) for (k, v) in d.items())