【问题标题】:Python: Apply function to values in nested dictionaryPython:将函数应用于嵌套字典中的值
【发布时间】:2016-01-01 07:41:31
【问题描述】:

我有一组任意深度的嵌套字典:

x = {'a': 1, 'b': {'c': 6, 'd': 7, 'g': {'h': 3, 'i': 9}}, 'e': {'f': 3}}

我想基本上将一个函数应用于字典中的所有整数,所以我猜像map,但对于嵌套字典。

所以:map_nested_dicts(x, lambda v: v + 7) 就是这样的目标。

我不知道存储密钥层然后将修改后的值放回正确位置的最佳方法。

最好的方法/方法是什么?

【问题讨论】:

  • 递归解决方案可能有效。遍历项目,如果值是整数,则更改它,如果值是字典,则在递归调用中传递它。

标签: python python-2.7 loops dictionary


【解决方案1】:

递归访问所有嵌套值:

import collections

def map_nested_dicts(ob, func):
    if isinstance(ob, collections.Mapping):
        return {k: map_nested_dicts(v, func) for k, v in ob.iteritems()}
    else:
        return func(ob)

map_nested_dicts(x, lambda v: v + 7)
# Creates a new dict object:
#    {'a': 8, 'b': {'c': 13, 'g': {'h': 10, 'i': 16}, 'd': 14}, 'e': {'f': 10}}

在某些情况下,需要修改原始 dict 对象(以避免重新创建它):

import collections

def map_nested_dicts_modify(ob, func):
    for k, v in ob.iteritems():
        if isinstance(v, collections.Mapping):
            map_nested_dicts_modify(v, func)
        else:
            ob[k] = func(v)

map_nested_dicts_modify(x, lambda v: v + 7)
# x is now
#    {'a': 8, 'b': {'c': 13, 'g': {'h': 10, 'i': 16}, 'd': 14}, 'e': {'f': 10}}

如果您使用的是 Python 3:

  • dict.iteritems替换为dict.items

  • import collections 替换为import collections.abc

  • collections.Mapping替换为collections.abc.Mapping

【讨论】:

  • 我想知道第一个函数中的 items 方法是否会造成麻烦,但看起来只有映射或映射派生使用此方法 - 有什么想法吗?
  • @wwii 这是某种鸭子类型...是的,它适用于dicts、dict 子类、ChainMap 和更多映射类型。
  • @wwii 但你可能是对的,我将 try..except 块替换为 isinstance(ob, collections.Mapping) 检查。
【解决方案2】:

只是为了扩展 vaultah 的答案,如果您的元素之一可以是列表,并且您也想处理这些:

import collections

def map_nested_dicts_modify(ob, func):
for k, v in ob.iteritems():
    if isinstance(v, collections.Mapping):
        map_nested_dicts_modify(v, func)
    elif isinstance(v, list):
        ob[k] = map(func, v)
    else:
        ob[k] = func(v)

【讨论】:

    【解决方案3】:

    如果您需要它在任意嵌套中同时适用于列表和字典:

    def apply_recursive(func, obj):
        if isinstance(obj, dict):  # if dict, apply to each key
            return {k: apply_recursive(func, v) for k, v in obj.items()}
        elif isinstance(obj, list):  # if list, apply to each element
            return [apply_recursive(func, elem) for elem in obj]
        else:
            return func(obj)
    

    【讨论】:

      【解决方案4】:

      我有一个更通用的实现,它可以接受任意数量的任何类型的容器作为参数。

      from collections.abc import Iterable
      import types
      def dict_value_map(fun, *dicts):
          keys = dicts[0].keys()
          for d in dicts[1:]:
              assert d.keys() == keys
          return {k:fun(*(d[k] for d in dicts)) for k in keys}
      def collection_map(fun, *collections):
          assert len(collections) > 0
          if isinstance(collections[0], dict):
              return dict_value_map(fun, *collections)
          if isinstance(collections[0], (tuple, list, set)):
              return type(collections[0])(map(fun, *collections))
          else:
              return map(fun, *collections)
      iscollection = lambda v:(isinstance(v,Iterable)and(not isinstance(v,str)))
      
      def apply(fun, *collections, at=lambda collections: not iscollection(collections[0])):
          '''
          like standard map, but can apply the fun to inner elements.
          at: a int, a function or sometype. 
          at = 0 means fun(*collections)
          at = somefunction. fun will applied to the elements when somefunction(elements) is True
          at = sometype. fun will applied to the elements when elements are sometype.
          '''
          if isinstance(at, int):
              assert at >= 0
              if at == 0:
                  return fun(*collections)
              else:
                  return collection_map(lambda *cs:apply(fun, *cs, at=at-1), *collections)
          if isinstance(at, types.FunctionType):
              if at(collections):
                  return fun(*collections)
              else:
                  return collection_map(lambda *cs:apply(fun, *cs, at=at), *collections)
          else:
              return apply(fun, *collections, at=lambda eles:isinstance(eles[0], at))
      

      例子:

      > apply(lambda x:2*x, [(1,2),(3,4)])  
      [(2, 4), (6, 8)]
      
      > apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]))
      ([6, 8], [10, 12])
      
      > apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]), at=1)  
      ([1, 2, 5, 6], [3, 4, 7, 8])
      
      > apply(lambda a,b: a+b, ([1,2],[3,4]), ([5,6],[7,8]), at=0)  
      ([1, 2], [3, 4], [5, 6], [7, 8])
      
      > apply(lambda a,b:a+b, {'m':[(1,2),[3,{4}]], 'n':5}, {'m':[(6,7),[8,{9}]],'n':10})  
      {'m': [(7, 9), [11, {13}]], 'n': 15}
      
      > apply(str.upper, [('a','b'),('c','d')], at=str)  
      [('A', 'B'), ('C', 'D')]
      

      > apply(lambda v:v+7, {'a': 1, 'b': {'c': 6, 'd': 7, 'g': {'h': 3, 'i': 9}}, 'e': {'f': 3}})
      {'a': 8, 'b': {'c': 13, 'd': 14, 'g': {'h': 10, 'i': 16}}, 'e': {'f': 10}}
      

      【讨论】:

      • 这很不高兴。我是 stackoverflow 的新手,不知道社区喜欢什么。如果答案应该被删除,那就去做吧,或者让我知道。 @k.jbaili
      猜你喜欢
      • 1970-01-01
      • 2018-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-29
      • 2021-12-04
      • 1970-01-01
      相关资源
      最近更新 更多