在Python字典中对值进行映射

344

给定一个字典 { k1: v1, k2: v2 ... },我希望在传递一个函数 f 的情况下获得 { k1: f(v1), k2: f(v2) ... }

是否有此类内置函数?还是我必须这样做:

dict([(k, f(v)) for (k, v) in my_dictionary.iteritems()])

理想情况下,我只需要写

my_dictionary.map_values(f)
或者
my_dictionary.mutate_values_with(f)

也就是说,无论原始字典被改变还是创建了一个副本,对我来说都没关系。


4
优化您的示例代码的更好方法是 dict((k, f(v)) for k, v in mydict.iteritems()),即去掉方括号,这可以通过生成器避免创建中间列表。 - bereal
9个回答

501
没有这样的功能;做这个最简单的方法是使用字典推导。
my_dictionary = {k: f(v) for k, v in my_dictionary.items()}

请注意,列表中也没有这样的方法;您需要使用列表推导或map()函数。
因此,您也可以使用map()函数来处理您的字典:
my_dictionary = dict(map(lambda (k, v): (k, f(v)), my_dictionary.items()))

但那真的不太易读。
(请注意,如果您仍在使用Python 2.7,应该使用.iteritems()方法而不是.items()来节省内存。此外,字典推导语法直到Python 2.7才被引入。)

15
+1:这也是我会做的。dict(zip(a, map(f, a.values())))稍微短一些,但我必须思考它在做什么,并提醒自己是的,如果字典不改变,则键和值以相同顺序迭代。我根本不需要考虑dictcomp在做什么,所以它是正确的答案。 - DSM
2
@chiborg:这是因为你现在使用的是“number-of-keys”次my_dictionary.__getitem__调用,而不是一次性查找所有键值对。 - Martijn Pieters
4
注意自从PEP3113(实现于Python 3.x)后,不再支持元组参数:lambda (k,v): (k, f(v))需要改写成类似lambda k_v: (k_v[0], f(k_v[1]))的形式。 - normanius
2
为什么取消了参数解包?这怎么算是改进呢? - WestCoastProjects
12
如果来自函数式编程语言,Python 看起来会非常笨拙。 - juanchito
显示剩余11条评论

41

9
因为有 z 这个字母,所以你知道这是一个很酷的图书馆。 - wordsforthewise
2
@wordsforthewise 炫酷的工具 - william_grisaitis

33

由于PEP-0469重命名iteritems()为items(),以及PEP-3113移除了Tuple参数拆包,在Python 3.x中你应该像Martijn Pieters♦的回答一样编写代码:

my_dictionary = dict(map(lambda item: (item[0], f(item[1])), my_dictionary.items()))

28

如果您不需要副本,可以直接在原字典中进行操作,而无需创建一个新字典,对于大型字典来说这可能更可取。

def mutate_dict(f,d):
    for k, v in d.iteritems():
        d[k] = f(v)

my_dictionary = {'a':1, 'b':2}
mutate_dict(lambda x: x+1, my_dictionary)

结果是my_dictionary包含如下内容:

{'a': 2, 'b': 3}

3
好的,你或许应该将mapdict重命名为mutate_values_with或其他名称,以使其非常明确地表明你正在重写字典值! :) - Tarrasch
2
zip(d.keys(), d.values())适用于更多的版本,而不是iteritems() - rassa45
1
@ytpillai 'zip'或者推导式会创建一个副本,而不是直接在原地改变值,这也是我回答的目的。当需要复制时,被接受的答案是最好的选择。 - gens
1
非常抱歉,我没有意识到您想要使用items方法。然而,对于非Python 2.7用户,还有另一种改进方法:{k:f(v) for k,v in iter(d.items())} - rassa45
1
通过创建迭代器来节省空间 - rassa45
显示剩余2条评论

4

虽然我的原始回答没有解决问题(尝试使用defaultdict工厂中访问键的解决方案来解决此问题),但我已经重新制定了解决当前问题的实际解决方案。

这就是它:

class walkableDict(dict):
  def walk(self, callback):
    try:
      for key in self:
        self[key] = callback(self[key])
    except TypeError:
      return False
    return True

使用方法:

>>> d = walkableDict({ k1: v1, k2: v2 ... })
>>> d.walk(f)

这个想法是通过继承原始的字典来赋予它所需的功能:对所有值应用“映射”函数。

好处在于,可以将该字典用作存储原始数据的dict,同时通过回调在请求时转换任何数据。

当然,你可以根据自己的喜好为类和函数命名(本答案中选择的名称受PHP的array_walk()函数启发)。

注意:尝试-捕获块或返回语句对于功能不是必需的,它们存在是为了进一步模仿PHP的array_walk行为。


1
这种方法无法解决 OP 的问题,因为对于已经存在的键,__missing__ 方法不会被调用,而我们希望对其进行转换。除非传递的工厂方法以某种方式使用原始字典作为回退,但由于这不是示例用法的一部分,我认为这并不是解决手头问题的令人满意的答案。 - Kaos
现有的键是哪些? - 7heo.tk
从OP:给定一个字典{k1:v1,k2:v2 ...}...。也就是说,你已经有一个dict开始了。 - Kaos
我想说我们两个都是对的,但我认为我们两个都是错的。你是对的,因为我的答案没有回答问题,但不是你所说的原因。我只是错过了重点,给出了一种在给定“[v1,v2,...]”时获取“{v1:f(v1),v2:f(v2),...}”的方法,而不是给定一个字典。我会编辑我的答案来纠正这个错误。 - 7heo.tk

4
为了避免在lambda内部进行索引,例如:
rval = dict(map(lambda kv : (kv[0], ' '.join(kv[1])), rval.iteritems()))

您还可以进行以下操作:

rval = dict(map(lambda(k,v) : (k, ' '.join(v)), rval.iteritems()))

1
这是第二个例子中2元组内部的巧妙操作。然而,它在lambda内部使用了自动元组解包,在Python 3中不再支持。因此,lambda(k,v)将无法工作。请参见https://dev59.com/lWEh5IYBdhLWcg3w5283 - Jonathan Komar

1

刚刚遇到了这个用例。我实现了 gens的答案,并添加了递归方法来处理也是字典的值:

def mutate_dict_in_place(f, d):
    for k, v in d.iteritems():
        if isinstance(v, dict):
            mutate_dict_in_place(f, v)
        else:
            d[k] = f(v)

# Exemple handy usage
def utf8_everywhere(d):
    mutate_dict_in_place((
        lambda value:
            value.decode('utf-8')
            if isinstance(value, bytes)
            else value
        ),
        d
    )

my_dict = {'a': b'byte1', 'b': {'c': b'byte2', 'd': b'byte3'}}
utf8_everywhere(my_dict)
print(my_dict)

当处理在Python 2中将字符串编码为字节的json或yaml文件时,这可能非常有用。


0
  • 遍历字典的方法
def f(x): return x+2
bill = {"Alice": 20, "Bob": 10}
d = {map(lambda x: f(x),bill.values())}
print('d: ',dict(d))

结果

: d:  {22: 12}
  • 在字典中对可迭代的值进行映射
bills = {"Alice": [20, 15, 30], "Bob": [10, 35]}
d= {map(lambda v: sum(v),bills.values())}
g= dict(map(lambda v: (v[0],sum(v[1])),bills.items()))
# prints
print('d: ',dict(d))
print('g: ',g)

结果

d:  {65: 45}
g:  {'Alice': 65, 'Bob': 45}

0
假设我们有一个字典d,我们想要对d的值进行函数映射。
d = {'a': 1,'b': 2, 'c': 3}

def f(x):
    return x + 1

result = dict(zip(d.keys(), map(f, d.values())))
print(result)

结果是
{'a': 2, 'b': 3, 'c': 4}

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