如何检查一个字符串是否包含字典

5
我希望能递归解析字典中所有的字符串值,并使用 ast.literal_eval(value) 对字符串进行评估,但如果字符串不包含字典,则不要进行评估。我之所以这样做,是因为字典中有一个字符串本身就是一个字典,我希望该值是一个字典。最好提供一个示例。
my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another string'}"}

现在我不想使用ast.literal_eval(my_dict ['c']),我希望有一个通用的解决方案,可以使用convert_to_dict(my_dict)

我想编写自己的方法,但我不知道如何检查一个字符串是否包含一个字典,然后ast.literal_eval将失败,因此提出了这个问题。


3
我的翻译如下:我认为策略是先进行评估,然后运行isinstance或类似的函数来检查它是否为字典类型。 - Adam Hopkins
你能给个例子吗?因为如果我递归地计算字典中的值,就会出错。 - Hakaishin
3
在try/except中使用literal_eval,尝试将其转换为字典,并在try中检查它是否是一个字典,然后将键重新赋值为新值。 - Padraic Cunningham
1
@PadraicCunningham,为什么不将这个回答变成一个答案呢? - Michael Foukarakis
一个字符串不能包含字典。它只能包含字典的表示 - Karl Knechtel
5个回答

3
您可以使用literal_eval函数后重新赋值以检查是否有一个字典:
from ast import literal_eval

def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                d[k] = evald
        except ValueError:
            pass

只需传入字典即可:
In [2]: my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another stri
   ...: ng'}"}

In [3]: reassign(my_dict)

In [4]: my_dict
Out[4]: {'a': 42, 'b': 'my_string', 'c': {'d': 33, 'e': 'another string'}}

In [5]: my_dict = {'a': '42', 'b': "my_string", '5': "{'d': 33, 'e': 'another st
...: ring', 'other_dict':{'foo':'bar'}}"}
In [6]: reassign(my_dict)  
In [7]: my_dict
Out[7]: 
{'5': {'d': 33, 'e': 'another string', 'other_dict': {'foo': 'bar'}},
 'a': '42',
 'b': 'my_string'}

你还需要知道的是,如果你的字典中包含某些其他对象,比如datetime对象等等,那么literal_eval将会失败,所以它是否能正常工作取决于你的字典中包含了哪些内容。
如果你需要递归处理,你只需要在新字典上调用reassign即可。
def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                d[k] = evald
                reassign(evald)
        except ValueError:
            pass

再次只需传递字典:

In [10]: my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': \"{'f' : 64}
    ...: \"}"}

In [11]: reassign(my_dict)

In [12]: my_dict
Out[12]: {'a': 42, 'b': 'my_string', 'c': {'d': 33, 'e': {'f': 64}}}

如果您想要一个新的字典:

from ast import literal_eval
from copy import deepcopy

def reassign(d):
    for k, v in d.items():
        try:
            evald = literal_eval(v)
            if isinstance(evald, dict):
                yield k, dict(reassign(evald))
        except ValueError:
            yield k, deepcopy(v)

这将为您提供一个新的字典:

In [17]: my_dict = {'a': [1, 2, [3]], 'b': "my_string", 'c': "{'d': 33, 'e': \"{
    ...: 'f' : 64}\"}"}

In [18]: new =  dict(reassign(my_dict))

In [19]: my_dict["a"][-1].append(4)

In [20]: new
Out[20]: {'a': [1, 2, [3]], 'b': 'my_string', 'c': {'d': 33, 'e': {'f': 64}}}

In [21]: my_dict
Out[21]: 
{'a': [1, 2, [3, 4]],
 'b': 'my_string',
 'c': '{\'d\': 33, \'e\': "{\'f\' : 64}"}'}

在处理类似上面的嵌套对象,如列表中的列表时,您需要确保使用deepcopy函数来获得真正的独立副本,否则您将无法得到一个真正的独立的字典副本。


2

这里有一个处理递归的建议。正如评论中所建议的,它尝试对所有内容进行评估,然后检查结果是否为字典,如果是,则递归,否则跳过该值。我稍微修改了初始字典以显示它可以很好地处理递归:

import ast
my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': \"{'f' : 64}\"}"}

def recursive_dict_eval(old_dict):
    new_dict = old_dict.copy()
    for key,value in old_dict.items():
        try:
            evaled_value=ast.literal_eval(value)
            assert isinstance(evaled_value,dict)
            new_dict[key]=recursive_dict_eval(evaled_value)

        except (SyntaxError, ValueError, AssertionError):
            #SyntaxError, ValueError are for the literal_eval exceptions
            pass
    return new_dict

print(my_dict)
print(recursive_dict_eval(my_dict))

输出:

{'a': 42, 'b': 'my_string', 'c': '{\'d\': 33, \'e\': "{\'f\' : 64}"}'}
{'a': 42, 'b': 'my_string', 'c': {'e': {'f': 64}, 'd': 33}}

1
其他答案真的很好,带领我找到了正确的解决方案,但之前被接受的答案存在一个错误。这是我的可行解决方案:
def recursive_dict_eval(myDict):
    for key,value in myDict.items():
        try:
            if(isinstance(value, dict)):
                recursive_dict_eval(value)
            evaled_value=ast.literal_eval(value)
            assert isinstance(evaled_value,dict)
            myDict[key]=recursive_dict_eval(evaled_value)

        except (SyntaxError, ValueError, AssertionError):
            #SyntaxError, ValueError are for the literal_eval exceptions
            pass
    return myDict

1

我上面评论中提到的一般思路是遍历字典并尝试评估。将其存储在本地变量中,然后检查该评估表达式是否为字典。如果是,则将其重新分配给传递的输入。如果不是,则保持不变。

my_dict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another string'}"}

def convert_to_dict(d):
    for key, val in d.items():
        try:
            check = ast.literal_eval(val)
        except:
            continue 
        if isinstance(check, dict):
            d[key] = check 
    return d

convert_to_dict(my_dict)

您可以在except块中使用continue和/或在except块后使用else:,以仅在未抛出异常时执行isinstance和赋值操作。这比在您知道它无效的情况下需要设置check = False并稍后测试要好。 - ShadowRanger

0

如果你需要处理嵌套的str定义dictjson.loadsobject_hook 可能适用于你:

import json

def convert_subdicts(d):
    for k, v in d.items():
        try:
            # Try to decode a dict
            newv = json.loads(v, object_hook=convert_subdicts)
        except Exception:
            continue
        else:
            if isinstance(newv, dict):
                d[k] = newv  # Replace with decoded dict
    return d

origdict = {'a': 42, 'b': "my_string", 'c': "{'d': 33, 'e': 'another string'}"}
newdict = convert_subdicts(origdict.copy())  # Omit .copy() if mutating origdict okay

这样应该递归地处理包含 dict 可能包含定义子字典的 str 值的情况。 如果您不需要处理该情况,则可以省略使用 object_hook,或完全用 ast.literal_eval 替换 json.loads


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