【问题标题】:More pythonic way to change multilevel dictionary values更改多级字典值的更多 Pythonic 方式
【发布时间】:2026-01-17 09:45:01
【问题描述】:

例如,我有以下字典:

{'foo': 'test', 
 'bar': {'test1': 'some text here'},
 'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}}}

或者类似的东西,也许更多的关卡。

现在的问题是,我想将...'some text here' 更改为'A test'。是的,我可以使用很多for 循环,如下代码:

d = {'foo': 'test',
     'bar': {'test1': 'some text here'},
     'baz': {'test2': {'test3': 'some text here',
                       'test4': 'some text here'}}}

for i in d:
    if d[i] == 'some text here':
        d[i] = 'A test'

    elif type(d[i]) == dict:
        for j in d[i]:
            if d[i][j] == 'some text here':
                d[i][j] = 'A test'

            elif type(d[i][j]) == dict:
                for n in d[i][j]:
                    if d[i][j][n] == 'some text here':
                        d[i][j][n] = 'A test'

__import__('pprint').pprint(d)

输出:

{'bar': {'test1': 'A test'},                                                    
 'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
 'foo': 'test'}

但是我认为这不是一个好方法...有什么想法吗?

【问题讨论】:

  • @user2393267: 那叫标签,不是标签:)
  • 替换的标准是什么,任意深度任意值的精确匹配?
  • @qarma:嗯,实际上我在我的问题中说过:也许更多级别

标签: python dictionary nested


【解决方案1】:

这看起来是一个很好的递归案例。

import re

def replace_rec(data, search, replace, _pattern=None):
    if _pattern is None:
        _pattern = re.compile(r'^%s$' % search)
    for k, v in data.items():
        try:
            data[k] = _pattern.sub(replace, v)
        except TypeError:
            try:
                replace_rec(data[k], search, replace, _pattern=_pattern)
            except AttributeError:
                # Leave any other types as they are.
                continue

将它用于您的示例,如下所示:

>>> data = {
...     'foo': 'test',
...     'bar': {'test1': 'some text here'},
...     'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}},
...     'loc': [1, 2, 3],
...     'fp': 'foo some text here foo',
... }
>>> replace_rec(data, 'some text here', 'A test')
>>> pprint.pprint(data)
{'bar': {'test1': 'A test'},
 'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
 'foo': 'test',
 'fp': 'foo some text here foo',
 'loc': [1, 2, 3]}

【讨论】:

    【解决方案2】:

    稍微另类的版本。这可以正确处理字典中的非字符串,并且只替换完全匹配的文本。

    def replace(d, find_text, replace_text):
        for k, v in d.items():
            if isinstance(v, dict):
                replace(v, find_text, replace_text)
            elif isinstance(v, str):
                if v == find_text:
                    d[k] = replace_text
    
    d = {
        'test': 'dont change some text here',
        'ignore' : 42,
    
        'foo': 'test', 
        'bar': {'test1': 'some text here'},
        'baz': {'test2': {'test3': 'some text here', 'test4': 'some text here'}}}       
    
    replace(d, 'some text here', 'A test')
    
    __import__('pprint').pprint(d)
    

    这将显示:

    {'bar': {'test1': 'A test'},
     'baz': {'test2': {'test3': 'A test', 'test4': 'A test'}},
     'foo': 'test',
     'ignore': 42,
     'test': 'dont change some text here'}
    

    【讨论】:

    • 好吧,我认为您的答案与 justinfay 之前接受的答案没有太大区别。所以他的答案有关于非字符串的问题,也许评论会比另一个答案更好。
    • 他的回答也将取代“不要在此处更改某些文本”。您的问题不清楚是否也需要更换这种情况。
    • 嗯...明白了,所以他的回答可以解决我的问题,这就够了。如果我的 dict 更复杂,你的问题会更有用。正确的?我赞成你的回答。
    • 你可能应该刚刚编辑了我的答案,包括检查它是否是一个字符串,因为你的函数结构完全相同。
    【解决方案3】:

    递归答案都很有趣和游戏,但您可以通过注意字典是可变的来获得更多 Pythonic:

    首先获取所有字典的引用(在所有级别),然后更改它们。

    def all_dicts(d):
        yield d
        for v in d.values():
            if isinstance(v, dict):
                yield from all_dicts(v)
    
    data = dict(foo=12, bar=dict(dd=12), ex=dict(a=dict(b=dict(v=12))))
    
    for d in all_dicts(data):
         for k, v in d.items():
             if v == 12:
                 d[k] = 33 
    

    【讨论】:

      【解决方案4】:

      只要您要更改的值不太麻烦,这里有一个单行:

      In [29]: data = dict(foo=12, bar=dict(dd=12), ex=dict(a=dict(b=dict(v=12))))
      
      In [30]: json.loads(re.sub("12", "33", json.dumps(data)))
      Out[30]: {'bar': {'dd': 33}, 'ex': {'a': {'b': {'v': 33}}}, 'foo': 33}
      

      编辑,根据 Kevin,简单的替换甚至不需要 re

      json.loads(json.dumps(data).replace("12", "33"))
      

      【讨论】:

      • 嗯...为什么我没有尝试这个...太棒了!顺便说一句,两次点击:1.如果键中有12,这也将替换键,也许使用': 33'而不是'33'? 2. 为什么不直接使用str.replace()
      • 好点,.replace() 更好!只要价值观足够独特。我专门针对可能的特殊情况使用了re,例如键。
      • 如果你打算以这种方式强制它,为什么当 str 和 eval 可以做你想做的事情时还要打扰 json eval(str({'foo': 33}).replace('33', '11'))
      • @justinfay 因为eval 是邪恶的,因为__repr__ 表示会产生不一致的引号['Jeanne', "d'Arc"]