将字典映射到另一个字典的一行表达式

19 投票
4 回答
23209 浏览
提问于 2025-04-16 10:00

我有一个字典,内容像这样:

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 个回答

9

在3.x版本中:

d = {m.get(key, key):value for key, value in d.items()}

这个方法是通过创建一个新的字典来实现的,这个字典里包含了来自d的所有值,并且这些值都被映射到一个新的键上。获取这个键的方式是这样的:m[key] if m in key else key,然后再用默认的.get函数(这个函数可以在键不存在时提供默认值)。

27

让我们来看看@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 中的某个键 km 中不存在,我们就保留它,所以默认值就是 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()]

我们知道每个项目都是一个包含两个部分的元组,所以我们可以把它展开成两个变量 kv

[(.., ..) for (k, v) in d.items()]

每个元组的第一个部分应该是来自 m 的正确键,或者如果 km 中不存在,就用 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'}

在一行代码中发生了很多事情。有些人说这很难读,但一旦你习惯了,把这段代码分成几行反而会显得难以理解。只要不要过度使用。列表推导和生成器表达式非常强大,但强大的能力也伴随着责任。为这个好问题点赞!

26

当然可以:

d = dict((m.get(k, k), v) for (k, v) in d.items())

撰写回答