【问题标题】:python collections.defaultdict on assignment but not on lookuppython collections.defaultdict 分配但不查找
【发布时间】:2019-01-10 20:58:34
【问题描述】:

我有以下代码:

from collections import *
nested_dict = lambda: defaultdict(nested_dict)
data = nested_dict()

这使我能够在字典中将任何新的“路径”写成一个衬里:

data['A']['B']['C']=3

这就是我想要的。 但是我想在运行时得到一个异常(对于任何不存在的路径):

var = data['A']['XXX']['C']

我觉得写作时需要 defaultdict,阅读时需要普通 dict...

或者,有没有一种简单的好方法来检查默认字典中是否存在“路径”而不修改其内容...

我尝试在查找之前将 defaultdict 转换回 dict,希望:

dict(data)['A']['XXX']['C']

会引发异常...但它不断创建缺少的键...

【问题讨论】:

  • dict(data) 是一个副本。
  • 所涉及的对象都没有任何方法可以区分您想要例外的var = data['A']['XXX']['C'] 和不应抛出的data['A']['XXX']['C']['D'] = 4 的第一步。

标签: python data-structures


【解决方案1】:

您无法在此处区分查找和写入,因为在 data['A']['B']['C'] = 3 分配中创建中间结构的是 查找。 Python 先执行索引操作data['A'],然后再执行['B'],然后再分配给'C' 键。 __getitem____setitem____missing__ 钩子没有提供足够的上下文来区分访问,然后在第二个示例中导致 'C' 分配与仅“阅读”'XXX'

你真的只有3个选项:

  • 不要使用defaultdict。编写时,显式使用dict.setdefault() 创建新的嵌套字典;您可以根据需要链接这些调用:

    var = {}
    var.setdefault('A', {}).setdefault('B', {})['C'] = 3
    

    或者你可以wrap recursive behaviour in a few functions

  • 完成编写后,创建defaultdict 结构的递归副本,以将其替换为dict 结构:

    def dd_to_d(dd):
        r = {}
        stack = [(r, dd)]
        while stack:
            target, dd = stack.pop()
            for key, value in dd.items():
                if isinstance(value, defaultdict):
                    sub = {}
                    stack.append((sub, value))
                    value = sub
                target[key] = value
        return r
    
    var = dd_to_d(var)
    
  • 将所有 default_factory 属性设置为 None 以禁用为缺少的键创建新值:

    def disable_dd(dd):
        stack = [dd]
        while stack:
            dd = stack.pop()
            dd.default_factory = None
            for key, value in dd.items():
                if isinstance(value, defaultdict):
                    stack.append(value)
    
    disable_dd(var)
    

【讨论】:

  • 谢谢。这只是解决方法......但也许这个问题没有更好的解决方案。我会将答案标记为有用(以表扬您的努力),但如果有人提出更好的建议,请为最终解决方案留出空间。
  • @user1159290:我不认为这是一种解决方法。如果您使用的是 defaultdict,那么您唯一的选择是将结构复制到字典或禁用工厂。另一种选择是不使用 defaultdict,dict 在这种情况下是显而易见的选择。
  • 好的。没有人有更好的解决方案可以建议,我接受了你的“解决方案”。但实际上这并不像我所希望的那么好(但也许是最好的)。在我的情况下,多个分配和查找是交错的,这使得提议的“解决方案”看起来都不好看。
  • @user1159290:我意识到我没有完全解释为什么这些是你的选择。我现在已将其添加到答案中。
  • @matijnp:谢谢。当然,当它发生时,我意识到发生了什么(在分配过程中进行了查找),但仍然希望可以做一些我不知道的事情以寻求更好的解决方案。无论如何谢谢
【解决方案2】:

一个明显的解决方案是只使用普通的 dicts 和一个可以“实现”中间键的函数:

def write_path(d, path, value):
    for key in path[:-1]:
        d = d.setdefault(key, {})
    d[path[-1]] = value

d = {}

write_path(d, ['a', 'b', 'c'], 3)
print(d)
print(d['a']['b']['c'])
print(d['a']['b']['d'])

输出

{'a': {'b': {'c': 3}}}
3
Traceback (most recent call last):
  File "writedefaultdict.py", line 11, in <module>
    print(d['a']['b']['d'])
KeyError: 'd'

【讨论】:

  • 这里也一样。不是一个真正的解决方案,但肯定是一个明显的可能解决方法。 (+1)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-22
  • 2023-03-29
  • 2016-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多