将字典映射到另一个字典的一行表达式
我有一个字典,内容像这样:
d = {'user_id':1, 'user':'user1', 'group_id':3, 'group_name':'ordinary users'}
还有一个“映射”字典,内容像这样:
m = {'user_id':'uid', 'group_id':'gid', 'group_name':'group'}
我想做的就是把第一个字典里的键用第二个字典里的值替换掉。期望的结果是:
d = {'uid':1, 'user':'user1', 'gid':3, 'group':'ordinary users'}
我知道字典的键是不可变的,也知道怎么用'if/else'语句来实现。
但是,也许有办法用一行代码来完成这个操作?
4 个回答
在3.x版本中:
d = {m.get(key, key):value for key, value in d.items()}
这个方法是通过创建一个新的字典来实现的,这个字典里包含了来自d
的所有值,并且这些值都被映射到一个新的键上。获取这个键的方式是这样的:m[key] if m in key else key
,然后再用默认的.get函数(这个函数可以在键不存在时提供默认值)。
让我们来看看@karlknechtel的优秀代码,它是干什么的:
>>> d = dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
那么它是怎么工作的呢?
要构建一个字典,你可以使用 dict()
函数。这个函数需要一个元组的列表。在 Python 3.x 和大于 2.7 的版本中,你还可以使用字典推导(可以参考@nightcracker的回答)。
我们先来分析一下 dict 的参数。首先,我们需要一个包含所有项目的列表,每个项目都是一个格式为 (key, value) 的元组。
>>> d.items()
[('group_id', 3), ('user_id', 1), ('user', 'user1'), ('group_name', 'ordinary users')]
给定一个键值 k
,我们可以通过 m[k]
从 m
中获取对应的值。
>>> k = 'user_id'
>>> m[k]
'uid'
不幸的是,并不是所有在 d
中的键在 m
中都存在。
>>> k = 'user'
>>> m[k]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'user'
为了解决这个问题,你可以使用 d.get(x, y)
,这个方法会返回 d[x]
如果键 x
存在,或者返回默认值 y
如果不存在。现在,如果 d
中的某个键 k
在 m
中不存在,我们就保留它,所以默认值就是 k
。
>>> m.get(k, k).
'user'
现在我们准备好构建一个元组列表来传递给 dict()
了。要在一行中构建一个列表,我们可以使用 列表推导。
如果要构建一个平方数的列表,你可以这样写:
>>> [x**2 for x in range(5)]
[0, 1, 4, 9, 16]
在我们的例子中,它看起来是这样的:
>>> [(m.get(k, k), v) for (k, v) in d.items()]
[('gid', 3), ('uid', 1), ('user', 'user1'), ('group', 'ordinary users')]
这有点复杂,我们再看看。
给我一个列表 [...]
,它由元组组成:
[(.., ..) ...]
我想要每个在 d
中的项目 x
对应一个元组:
[(.., ..) for x in d.items()]
我们知道每个项目都是一个包含两个部分的元组,所以我们可以把它展开成两个变量 k
和 v
。
[(.., ..) for (k, v) in d.items()]
每个元组的第一个部分应该是来自 m
的正确键,或者如果 k
在 m
中不存在,就用 k
,第二部分是来自 d
的值。
[(m.get(k, k), v) for (k, v) in d.items()]
我们可以把它作为参数传递给 dict()
。
>>> dict([(m.get(k, k), v) for (k, v) in d.items()])
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
看起来不错!但是你可能会说,@karlknechtel 没有使用方括号。
没错,他没有使用列表推导,而是使用了 生成器表达式。简单来说,区别在于列表推导会把整个列表存储在内存中,而生成器表达式则是一次计算一个项目。如果列表只是作为一个中间结果,通常使用生成器表达式是个好主意。在这个例子中其实没有太大区别,但养成这个习惯是好的。
等价的生成器表达式看起来是这样的:
>>> ((m.get(k, k), v) for (k, v) in d.items())
<generator object <genexpr> at 0x1004b61e0>
如果你把生成器表达式作为参数传递给一个函数,通常可以省略外面的括号。最后,我们得到:
>>> dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
在一行代码中发生了很多事情。有些人说这很难读,但一旦你习惯了,把这段代码分成几行反而会显得难以理解。只要不要过度使用。列表推导和生成器表达式非常强大,但强大的能力也伴随着责任。为这个好问题点赞!
当然可以:
d = dict((m.get(k, k), v) for (k, v) in d.items())