如何设置嵌套的Python字典中给定列表位置的第n个元素?

7
如果我有一个字典,例如
d = { 'a': {
            "x": [],
            "y": {
                "z": {
                    "1": 'loser'
                        }
                 }
            }
    }

print(d['a']['y']['z']['1']) #=> loser

然而,我不知道这个字典中有多少层嵌套的条目。相反,我有一组键值,如下所示:

['a', 'y', 'z', '1']

如何优雅地设置d['a']['y']['z']['1'] = 'winner'

这是我尝试过的方法:

l = ['a', 'y', 'z', '1']

def change_the_value(d, l, value):
    if len(l) == 1:
        d[l[0]] = value
    if len(l) == 2:
        d[l[0]][l[1]] = value
    if len(l) == 3:
        d[l[0]][l[1]][l[2]] = value
    if len(l) == 4:
        d[l[0]][l[1]][l[2]][l[3]] = value
    # ... ad infinitum
    return d

change_the_value(d, l, 'winner')
print(d) # => {'a': {'x': [], 'y': {'z': {'1': 'winner'}}}}
4个回答

6
您可以使用简单的for循环:
_path = ['a', 'y', 'z', '1']
d = {'a': {'x': [], 'y': {'z': {'1': 'loser'}}}}
_start = d
for i in _path[:-1]:
   _start = _start[i]

_start[_path[-1]] = 'winner'
print(d)

输出:

{'a': {'x': [], 'y': {'z': {'1': 'winner'}}}}

您也可以使用递归(如果您不介意为d创建一个新结构):
def update(_d, _path):
  return {a:'winner' if a == _path[-1] and len(_path) == 1 else 
    update(b, _path[1:]) if a == _path[0] else b for a, b in _d.items()}

print(update(d, ['a', 'y', 'z', '1']))

输出:

{'a': {'x': [], 'y': {'z': {'1': 'winner'}}}}

感谢您分享您的知识。我最初也是从类似这样的东西开始,但由于我不理解字典的复制是如何修改相同的内存空间,所以无法完成它。 - Conner
@Conner 实际上,_start 中有对 d 的引用。因此,对 _start 的任何变异也将出现在 d 中。 - Ajax1234

6

如果你可以确定键列表是有效的,你可以使用functools.reduce

>>> from functools import reduce 
>>>
>>> keys = ['a', 'y', 'z', '1']                                                                                     
>>> d = { 'a': { 
...:             "x": [], 
...:             "y": { 
...:                 "z": { 
...:                     "1": 'loser' 
...:                         } 
...:                  } 
...:             } 
...:     }                                                                                                             
>>>                                                                                                                    
>>> the_dict = reduce(dict.get, keys[:-1], d)                                                                          
>>> the_dict[keys[-1]] = 'winner'                                                                                      
>>> d                                                                                                                  
{'a': {'x': [], 'y': {'z': {'1': 'winner'}}}}

3
这应该可以解决任何长度为l的问题!
tmp = d
for key in l[:-1]:
    tmp = tmp[key]

tmp[l[-1]] = 'winner'

我有一种感觉,一定有更好的方法,但这至少消除了所有那些if语句。

不,那很好。+1 - timgeb
我有点喜欢@timgeb的reduce版本,因为嘿,它只有一行代码!根据reduce如何实现其内部循环与我的Python级别的for循环相比,它可能更快。我喜欢我的版本更加直观,因为即使我经常使用functools,我也不得不查找reduce的签名。两者显然都有优点! - Scott Colby

0

如果您有一个键列表,那么还有其他好的答案。我注意到您的示例正在寻找第一个字符串值。如果这总是正确的,那么这里有一个解决方案可以在没有键列表的情况下工作:

def setval(d, val):
  for i in d:
    if isinstance(d[i], dict):
      return setval(d[i], val)
    if isinstance(d[i], str):
      d[i] = val
      return
  return None

setval(d, 'winner')
print(d)

或者,如果您想使用列表来指定路径,这里有一种递归方法:

def setvalat(d, l, val):
  if len(l) > 1:
    setvalat(d[l[0]], l[1:], val)
  else:
    d[l[0]] = val

setvalat(d, ['a', 'y', 'z', '1'], 'winner')
print(d)

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