【问题标题】:Python adding/updating dict element of any depthPython添加/更新任何深度的dict元素
【发布时间】:2015-11-12 02:17:33
【问题描述】:

有这样的字典

my_pets = {
    'Rudolf': {
        'animal': 'cat', 
        'legs': 4
    }
}

实现以下等价物的更清洁方法是什么?

my_pets['Rudolf']['legs']['front-right']['injured'] = True
my_pets['Rudolf']['legs']['front-left']['injured'] = False

它应该更新为

my_pets = {
    'Rudolf': {
        'animal': 'cat', 
        'legs': {
            'front-right': {'injured':True},
            'front-left': {'injured':False}
        }
    }
}

【问题讨论】:

  • 我认为您想在这里创建一些类,而不是将所有数据放入嵌套的字典中。这是我能想到的让它“更干净”的唯一方法。注意:使用您当前的数据表示,您的方式是最简洁的访问方式
  • 稍微编辑了这个问题,我现在的丑陋之处在于,我不得不通过检查它们的存在并在不存在时创建空字典并移动到下一个深度来手动链接它们。
  • 使用collections.defaultdict(lambda: collections.defaultdict) 而不是字典。这可能有助于更新过程
  • @inspectorG4dget 这是正确的方向,但不会超过一个级别,因为在顶层访问缺少的键会创建一个 defaultdict 而没有初始化默认工厂。因此,每当您尝试访问 second 级别的缺失键时,都会引发 KeyError

标签: python dictionary


【解决方案1】:

您可以创建一个“无限”的默认字典,如下所示:

from collections import defaultdict

def infinidict():
    return defaultdict(infinidict)

然后写:

>>> my_pets = infinidict()
>>> my_pets['Rudolf']['animal'] = 'cat'
>>> my_pets['Rudolf']['weight'] = 3
>>> my_pets['Rudolf']['legs']['front-right']['injured'] = True
>>> my_pets
defaultdict(<function __main__.infinidict>,
            {'Rudolf': defaultdict(<function __main__.infinidict>,
                         {'animal': 'cat',
                          'legs': defaultdict(<function __main__.infinidict>,
                                      {'front-right': defaultdict(<function __main__.infinidict>,
                                                   {'injured': True})}),
                          'weight': 3})})

输出看起来很乱,但my_pets 可以在任何需要dict 的地方使用。

【讨论】:

  • 巧妙的把戏。我一直认为defaultdict 的“深度有限”是一个很大的缺点,没想到会做这样的事情。
【解决方案2】:

下面是一个字典子类,它允许丢失任意深度的键:

class freedict(dict):
    # called when trying to read a missing key
    def __missing__(self, key):
        self[key] = freedict()
        return self[key]

    # called during attribute access
    # note that this invokes __missing__ above
    def __getattr__(self, key):
        return self[key]

    # called during attribute assignment
    def __setattr__(self, key, value):
        self[key] = value

可以这样使用(对密钥的属性访问是个人喜好):

d = freedict()
d['one']['two']['three'] = 1
d.one.two.three = 2

【讨论】:

  • 相当干净,我认为比递归默认字典更可取,因为打印实例看起来就像使用 dict 得到的一样。
  • 听起来很整洁,但在少数情况下可能会损坏,例如:d = freedict()⏎ d['one'] = 1⏎ d['one']['two']['three'] = 1
  • @itsneo 是的,但是在这种情况下您还期望发生什么?我认为它应该打破。
  • 我想你可以编写一个可以在内部节点保存值的树数据结构。但到那时,它不再是真正的dict,它可能需要有一种特殊的方式来访问值。我不认为像你在评论中写的那样的 API 实际上是可能的,尽管我会考虑更多。
  • @igor,我明白了,但在上面的破例中,我们可以预期 d['one']['two']['three'] = 1 会产生 {'one': {'two': {'three': 1}}},这是您的原始代码产生的。但是,如果 d['one'] 已经出现,则无法替换它们。记住一个普通的 dict 赋值是如何工作的,它只是替换现有的值,这不会发生在这里。
【解决方案3】:

这是一个很有趣也很实际的情况,曾经可以遇到。 有许多实现,每个都解决了某些问题,并错过了一些边缘场景。

可以在这些标题中找到可能的解决方案和不同的答案。

What is the best way to implement nested dictionaries?

What's the best way to initialize a dict of dicts in Python?

Set nested dict value and create intermediate keys

此外,还有许多关于“自动生存”要求的要点和博客,包括维基百科的存在。

http://blog.yjl.im/2013/08/autovivification-in-python.html

https://news.ycombinator.com/item?id=3881171

https://gist.github.com/hrldcpr/2012250

https://en.wikipedia.org/wiki/Autovivification

http://blogs.fluidinfo.com/terry/2012/05/26/autovivification-in-python-nested-defaultdicts-with-a-specific-final-type/

虽然一旦边缘情况仍然存在问题,上述实现很方便。在写这篇文章的时候,没有一个实现可以很好地处理是否有一个原始的坐和阻塞嵌套。

这是 StackOverflow 中回答此问题和相关问题的 3 种主要方式。

  • 编写一个辅助方法,它接受字典、值和嵌套键列表 适用于普通的 dict 对象,但缺少通常的方括号语法,

  • 使用 Defaultdict 并编写自定义类,从根本上说这是可行的,因为默认 dict 为缺少的键提供 {} 很好的语法,但仅适用于使用自定义类创建的对象。

  • 使用元组来存储和检索 (https://stackoverflow.com/a/651930/968442) 最糟糕的想法,甚至不应该被称为解决方案,这就是为什么

    mydict = {}
    mydict['foo', 'bar', 'baz'] = 1
    print mydict['foo', 'bar', 'baz']

    可以正常工作,但是当您访问 mydict['foo', 'bar'] 时,期望将是 {'baz':1},而不是 KeyError 这基本上破坏了可迭代和嵌套结构的想法

在这三种方法中,我的选择是选项 1。通过编写一个微小的辅助方法,可以务实地解决边缘情况,这是我的实现。

def sattr(d, *attrs):
    # Adds "val" to dict in the hierarchy mentioned via *attrs
    for attr in attrs[:-2]:
        # If such key is not found or the value is primitive supply an empty dict
        if d.get(attr) is None or isinstance(d.get(attr), dict):
            d[attr] = {}
        d = d[attr]
    d[attrs[-2]] = attrs[-1]

现在

my_pets = {'Rudolf': {'animal': 'cat', 'legs': 4}}
sattr(my_pets, 'Rudolf', 'legs', 'front-right', 'injured', True)
sattr(my_pets, 'Rudolf', 'legs', 'front-left', 'injured', False)

会产生

{'Rudolf': {'legs': 4, 'animal': 'cat'}}
{'Rudolf': {'legs': {'front-right': {'injured': True}}, 'animal': 'cat'}}
{'Rudolf': {'legs': {'front-left': {'injured': False}, 'front-right': {'injured': True}}, 'animal': 'cat'}}

【讨论】:

    【解决方案4】:

    尝试使用try

    try:
    
        # checks whether 'front-right' exists. If exists assigns value. Else raises   exception
        my_pets['Rudolf']['legs']['front-right']= {'injured':True}}
    
    except:
    
        # On raising exception add 'front-right' to 'legs'
        my_pets['Rudolf']['legs'] = {'front-right': {'injured':True}}
    

    这应该可以工作

    【讨论】:

      【解决方案5】:

      这将允许您根据列表向字典添加任意深度的键 按键。

        def add_multi_key(subscripts, _dict={}, val=None):
              """Add an arbitrary length key to a dict.
      
              Example:
      
                  out = add_multi_key(['a','b','c'], {}, 1 )
      
                  out -> {'a': {'b': {'c':1}}}
      
              Arguments:
                  subscripts, list of keys to add
                  _dict, dict to update. Default is {}
                  val, any legal value to a key. Default is None.
      
              Returns:
                  _dict - dict with added key.
              """
              if not subscripts:
                  return _dict
              subscripts = [s.strip() for s in subscripts]
              for sub in subscripts[:-1]:
                  if '_x' not in locals():
                      if sub not in _dict:
                          _dict[sub] = {}
                      _x = _dict.get(sub)
                  else:
                      if sub not in _x:
                          _x[sub] = {}
                      _x = _x.get(sub)
              _x[subscripts[-1]] = val
              return _dict
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-21
        • 2010-10-18
        • 2018-12-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多