从Python字典中查找嵌套值的一行代码

9
假设我有一个任意嵌套的字典:
d = {
    11: {
        21: {31: 'a', 32: 'b'},
        22: {31: 'a', 34: 'c'},
    },
    12: {
        1: {2: 3}
    }
}

还有一个键列表,其位置告诉我在哪个嵌套字典中查找每个键:

keys = [11, 21, 31]
# keys = [11, 23, 44]

有没有一个简单的一行代码来实现这个?我看了下面列出的问题,它们是类似的,但不是我要找的。我也尝试过自己做,得到了这个:
from functools import reduce

def lookup(d, key):
    return d.get(key, {}) if d and isinstance(d, dict) else None

def fn(keys, d):
    return reduce(lookup, keys, d)

print(fn(keys, d)) # prints 'a'

这样做的问题在于,对于第二组键(请参见被注释的键),它会继续查找嵌套键,即使找不到更高级别的键也会继续查找,这样继续是毫无意义的。 我怎样才能在找到最终匹配或失败时立即停止reduce(下面列出的问题之一解决了这个问题,但我实际使用时无法应用...还是可以吗?)? 有其他想法吗?哦,我想仅使用官方Python库来完成此操作。因此没有numpypandas等,但functoolsitertools可以使用。

Python:将列表转换为带有异常处理的多维字典键

{{link2:在Python中访问嵌套字典的每个元素是否有简单的一行代码?}}

在Python 3.3中访问嵌套字典中的嵌套值

使用itertools进行递归函数应用

停止Reduce()操作。部分运行总和的函数式方法

递归查找字典中的键

谢谢!


不确定我是否理解您想要做什么,这会有所帮助吗? d[keys[0]][keys[1]][keys[2]] 如果存在最终匹配项,则会找到它,否则将失败。 - lc123
这正是我想要的,但我不想使用索引,因为列表的长度可以是任意的。 - Bahrom
2
如果您将dict.__getitem__用作reduce函数,当类型错误或未找到键时,它将引发异常。我认为这符合您提前停止的需求。只需在reduce周围加上try块即可。 - aghast
3个回答

13
你可以使用functools.reduce()函数:
from functools import reduce # In Python 2, don't import it. (It's a built-in)

print(reduce(dict.get, keys, d))

# 'a'

你提到的键,操作如下:
  • 使用d(初始)和keys的第一个项目(11)调用dict.get以获取d[11]
  • 使用结果(字典)和keys中的下一个项(21)调用dict.get以获取{...}[21]
  • 调用dict.get ...
    ...

直到keys被"缩减"到最终值('a')

编辑:由于如果没有该键,dict.get会导致返回None,可能会产生不希望的结果。如果想要一个KeyError,可以使用operator.getitem


1
你可以用 dict.__getitem__ 替换 lambda。 - Norman
1
@Norman 我本来也想建议这个,但是想先检查一下方法,而且这台电脑上没有安装 Python!但是没错,对你的建议点赞。 - Alex Van Liew
3
print(reduce(dict.get, keys, d)) - Padraic Cunningham
1
@PadraicCunningham:是的,谢谢。我更喜欢这种方式而不是dict.__getitem__ - zondo
1
@zondo:只有在最后一个键查找返回None时,它才会抛出错误。如果您执行最终查找(比如 [11, 21, 33]),但没有找到,则会返回 None,但如果您有 [11, 23, 32],它可能会引发有关无法索引NoneType的令人困惑的异常;我只是觉得这不一致。直接使用魔术方法并不依赖于内部工作方式;我很确定它们在规范中被定义,并且需要对实现索引的任何对象有效(毕竟,d[x]基本上是语法糖,用来表示dict.__getitem__(d,x))。 - Alex Van Liew
显示剩余6条评论

0

这是我想出的一个解决方案,当给出无效的查找路径时,它还会返回有用的信息,并允许您浏览任意JSON,包括嵌套的列表和字典结构。(抱歉它不是一行代码解决的)。

def get_furthest(s, path):
    '''
    Gets the furthest value along a given key path in a subscriptable structure.

    subscriptable, list -> any
    :param s: the subscriptable structure to examine
    :param path: the lookup path to follow
    :return: a tuple of the value at the furthest valid key, and whether the full path is valid
    '''

    def step_key(acc, key):
        s = acc[0]
        if isinstance(s, str):
            return (s, False)
        try:
            return (s[key], acc[1])
        except LookupError:
            return (s, False)

    return reduce(step_key, path, (s, True))

-2
d = {
    11: {
        21: {
            31: 'a from dict'
        },
    },
}

l = [None] * 50
l[11] = [None] * 50
l[11][21] = [None] * 50
l[11][21][31] = 'a from list'

from functools import reduce

goodkeys = [11, 21, 31]
badkeys = [11, 12, 13]

print("Reducing dictionary (good):", reduce(lambda c,k: c.__getitem__(k), goodkeys, d))
try:
    print("Reducing dictionary (bad):", reduce(lambda c,k: c.__getitem__(k), badkeys, d))
except Exception as ex:
    print(type(ex), ex)

print("Reducing list (good):", reduce(lambda c,k: c.__getitem__(k), goodkeys, l))

try:
    print("Reducing list (bad):", reduce(lambda c,k: c.__getitem__(k), badkeys, l))
except Exception as ex:
    print(type(ex), ex)

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