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

19
我有一个类似字典的东西。
d = {'user_id':1, 'user':'user1', 'group_id':3, 'group_name':'ordinary users'}

并且有一个类似于“映射”(mapping)的字典:

m = {'user_id':'uid', 'group_id':'gid', 'group_name':'group'}

我想要做的就是用第二个字典中的值"替换"第一个字典中的键。期望的结果如下:

d = {'uid':1, 'user':'user1', 'gid':3, 'group':'ordinary users'}
我知道键是不可变的,也知道如何使用“if/else”语句来实现。但是也许有一种方法可以在一行表达式中完成吗?

uid是第二个值,而不是键。您想看到什么输出? - Senthil Kumaran
4个回答

26

我们来看一下@karlknechtel的优秀代码并了解其作用:

>>> d = dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
但是它是如何工作的?
要构建一个字典,可以使用dict()函数。它需要一个元组列表作为参数。在3.x和>2.7中,您还可以使用字典理解(请参见@nightcracker的答案)。
让我们分析一下字典的参数。首先,我们需要m中所有项的列表。每个项都是格式为(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),如果键 x 存在,则返回 d[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中正确的键作为第一个组成部分,或者如果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'}

一行代码中发生了很多事情。有些人说这种写法难以阅读,但是一旦你习惯了,将这段代码拆成几行反而会变得难以阅读。只要不过度使用就可以了。列表推导式和生成器表达式非常强大,但是伴随着强大的能力也有伟大的责任。对于这个好问题给予+1!


25
当然:
d = dict((m.get(k, k), v) for (k, v) in d.items())

2
应该使用 for (k, v) in d.items 而不是 for (k, v) in d - mouad
已修复。 (并重新修复; items需要被调用为方法。) - Karl Knechtel
2
对于Python 2.x版本,使用d.iteritems()而不是d.items()会更合适(但这只在处理非常大的字典时才有影响)。 - Sven Marnach

9

In 3.x:

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

它的工作原理是创建一个新字典,其中包含从d中获取的每个值,并映射到一个新键。检索键的方法如下:m[key] if m in key else key,但使用默认的.get函数(如果键不存在,则支持默认值)。

2

为什么要在一行中执行它?


result = {}
for k, v in d.iteritems():
    result[m.get(k, k)] = v

2
因为列表/字典/任何推导式非常符合Python的风格,而且运行速度非常快? - orlp
"Pythonic",没错,基本上就是这样。 - Karl Knechtel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接