【问题标题】:How to mask a Python 3 nested dictionary to return a new dictionary with only certain items?如何屏蔽 Python 3 嵌套字典以返回仅包含某些项目的新字典?
【发布时间】:2018-02-28 21:03:18
【问题描述】:

我正在使用 API 的多个端点,这些端点在返回的数据中非常冗长。我想将此数据的一个子集提供给其他地方的另一段代码。

假设给了我几个这样的字典(我打算循环并过滤):

asset = {
    'id': 1,
    'name': 'MY-PC',
    'owner': 'me',
    'location': 'New York City',
    'model': {
        'id': 1,
        'name': 'Surface',
        'manufacturer': {
            'id': 1,
            'name': 'Microsoft'
        }
    }
}

我想创建一个函数来接收该字典,以及一个“掩码”,该掩码将用于创建一个仅包含允许项的新字典。这可能是一个示例掩码(不过,我可以使用使生成的代码最简洁的任何格式):

mask = {
    'id': True,
    'name': True,
    'model': {
        'id': True,
        'name': True,
        'manufacturer': {
            'name': True
        }
    }
}

然后函数应该返回这个:

mask = {
    'id': 1,
    'name': 'MY-PC',
    'model': {
        'id': 1,
        'name': 'Surface',
        'manufacturer': {
            'name': 'Microsoft'
        }
    }
}

Python 3 中是否已经内置了一些可以帮助解决此问题的东西?看起来如果我必须手动执行此操作,它会很快变得非常难看。我找到了itertools.compress,但这似乎是用于列表的,无法处理字典的复杂性。

【问题讨论】:

  • 你听说过jq(1)吗?
  • 当掩码键没有匹配的数据键时会发生什么?

标签: python dictionary iterator


【解决方案1】:

您可以通过仅选择主字典中对应的值来递归地从掩码构建一个新字典:

def prune_dict(dct, mask):
    result = {}
    for k, v in mask.items():
        if isinstance(v, dict):
            value = prune_dict(dct[k], v)
            if value: # check that dict is non-empty
                result[k] = value
        elif v:
            result[k] = dct[k]
    return result

print(prune_dict(asset, mask))

{'id': 1,
'model': {'id': 1, 'manufacturer': {'name': 'Microsoft'}, 'name': 'Surface'},
'name': 'MY-PC'}

【讨论】:

    【解决方案2】:

    这将是一个使用递归的好机会,这里有一些我没有测试过的示例代码:

    def copy(asset, result, mask):
        for key_name, value in mask.items():
            if value == True:
                result[key_name] = asset[key_name]
            else:
                result[key_name] = x = {}
                copy(asset[key_name], x, value)
    
    y = {}
    copy(asset, y, mask)
    

    【讨论】:

      【解决方案3】:

      这可能是一个递归函数。另外,对于面具,我推荐这种格式:mask = ["id", "name", "model.id", "model.name", "model.manufacturer.name"]

      然后,您首先只保留掩码中命名的条目:

      def filterstage1(dictionary, mask):
          result = {}
          for key in dictionary:
              if isinstance(dictionary[key], dict):
                  newmask = [maskname[mask.find(".") + 1:] for maskname in mask if maskname.startswith(key + ".")]
                  result[k] = filterstage1(dictionary[key], newmask)
              elif key in mask:
                  result[key] = dictionary[key]
          return result
      

      然后,根据您是否要删除不在掩码中且没有子元素的子字典,您可以包含第二阶段:

      def filterstage2(dictionary, mask):
          result = {}
          for key in dictionary:
              if not (isinstance(dictionary[key], dict) and dictionary[key] == {} and key not in mask):
                  result[key] = dictionary[key]
      

      最终代码:filterstage2(filterstage1(dictionary, mask), mask)。如果您愿意,可以将这两个阶段组合在一起。

      【讨论】:

      • P.S.查看其他答案;这个解决方案对于生产代码可能不是很好:P
      猜你喜欢
      • 2011-03-26
      • 2013-11-30
      • 1970-01-01
      • 1970-01-01
      • 2019-06-26
      • 1970-01-01
      • 2020-08-27
      • 2022-06-11
      相关资源
      最近更新 更多