Converter uma representação String de um dicionário em um dicionário?


768

Como posso converter a strrepresentação de a dict, como a seguinte string, em uma dict?

s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"

Eu prefiro não usar eval. O que mais posso usar?

A principal razão para isso, é uma das minhas aulas de colegas de trabalho que ele escreveu, converte todas as entradas em strings. Não estou com disposição para modificar suas aulas, para lidar com esse problema.


1
Se você não pode usar o Python 2.6, você pode usar uma implementação simples de segurança segura, como code.activestate.com/recipes/364469 Ele faz carona no compilador Python, para que você não precise fazer todo o trabalho pesado.
Ned Batchelder

11
Nota : Para aqueles que vêm aqui com dados JSON de aparência enganosamente semelhante , você deve ler o Parse JSON no Python . JSON não é a mesma coisa que Python . Se você tiver aspas duplas em suas cadeias, provavelmente terá dados JSON. Você também pode procurar , ou , a sintaxe do Python usa , e . "nulltruefalseNoneTrueFalse
Martijn Pieters

Respostas:


1167

A partir do Python 2.6, você pode usar o built-in ast.literal_eval:

>>> import ast
>>> ast.literal_eval("{'muffin' : 'lolz', 'foo' : 'kitty'}")
{'muffin': 'lolz', 'foo': 'kitty'}

Isso é mais seguro do que usar eval. Como seus próprios documentos dizem:

>>> ajuda (ast.literal_eval)
Ajuda na função literal_eval no módulo ast:

literal_eval (node_or_string)
    Avalie com segurança um nó de expressão ou uma sequência que contém um Python
    expressão. A cadeia ou nó fornecido pode consistir apenas nos seguintes
    Estruturas literais em Python: strings, números, tuplas, listas, dictes, booleanos,
    e nenhum.

Por exemplo:

>>> eval("shutil.rmtree('mongo')")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "/opt/Python-2.6.1/lib/python2.6/shutil.py", line 208, in rmtree
    onerror(os.listdir, path, sys.exc_info())
  File "/opt/Python-2.6.1/lib/python2.6/shutil.py", line 206, in rmtree
    names = os.listdir(path)
OSError: [Errno 2] No such file or directory: 'mongo'
>>> ast.literal_eval("shutil.rmtree('mongo')")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/Python-2.6.1/lib/python2.6/ast.py", line 68, in literal_eval
    return _convert(node_or_string)
  File "/opt/Python-2.6.1/lib/python2.6/ast.py", line 67, in _convert
    raise ValueError('malformed string')
ValueError: malformed string

Devo acrescentar que você precisa limpar a string para usar com ast.literal_eval. (garantir citações / aspas duplas em seqüência de escaparem)
Paulo Matos

Eu recebo esse erro Estou no python 2.6 (x86) no Windows 7 x64 Arquivo "D: \ Python26 \ lib \ ast.py", linha 48, em literal_eval node_or_string = parse (node_or_string, mode = 'eval') Arquivo "D : \ Python26 \ lib \ ast.py ", linha 36, ​​na análise retornar compilação (expr, filename, mode, PyCF_ONLY_AST) Arquivo" <unknown> ", linha 1 ^ SyntaxError: sintaxe inválida

e quanto às "dict(a=1)"cordas de estilo?
N611x007

Isso não parece funcionar com um valor enum dentro de um dicionário. Por exemplo: d = "{'col': <Colors.RED: 2>, 'val': 2}"
shivshnkr

3
por que não usar json.dumps e json.loads INSEAD, eu encontrei esta solução mais Elevant thant usando eval
Auros132

232

https://docs.python.org/3.8/library/json.html

O JSON pode resolver esse problema, embora seu decodificador deseje aspas duplas em torno de chaves e valores. Se você não se importa com um hack de substituição ...

import json
s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
json_acceptable_string = s.replace("'", "\"")
d = json.loads(json_acceptable_string)
# d = {u'muffin': u'lolz', u'foo': u'kitty'}

Observe que se você tiver aspas simples como parte de suas chaves ou valores, isso falhará devido à substituição incorreta de caracteres. Esta solução é recomendada apenas se você tiver uma forte aversão à solução de avaliação.

Mais sobre aspas simples de json: jQuery.parseJSON lança erro "JSON inválido" devido a aspas simples escapadas em JSON


12
{"foo": "b'ar"}
Mark E. Haase

4
{'foo': (1, 2, 3)}
Mark E. Haase

1
Eu estava procurando por esta solução. +1para informar que o decodificador deseja aspas duplas em torno de chaves e valores.
H8pathak

Outro problema é para "{0: 'Hello'}".
Finn Årup Nielsen 23/08/19

3
Isso também falha se você tiver vírgulas à direita (não compatíveis com JSON), por exemplo: "{'muffin': 'lolz', 'foo': 'kitty',}"
guival

159

usando json.loads:

>>> import json
>>> h = '{"foo":"bar", "foo2":"bar2"}'
>>> d = json.loads(h)
>>> d
{u'foo': u'bar', u'foo2': u'bar2'}
>>> type(d)
<type 'dict'>

13
Acho que não responde à resposta do OP. Como usamos json.laads para converter uma string s = "{'muffin': 'lolz', 'foo': 'kitty'}" para ditar?
21816 technechi

por que essa impressão 'u' está na saída? por exemplo - str = '{"1": "P", "2": "N", "3": "M"}' d = json.loads (str) imprime a saída d é: {u'1 ': u'P ', u'3': u'M ', u'2': u'N '}
user905

2
@technazi: json.loads (h.replace ("'",' "'))
ntg

No entanto, existem limites, por exemplo: h = '{"muffin": "lolz", "foo": "gatinho",}', também h = '{"muffin's": "lolz", "foo": "gatinho "} ', (acabei de notar parte dos mesmos comentários em uma resposta semelhante ... ainda deixando aqui por completo ...)
ntg 08/12/16

4
Na minha opinião, essa é a maneira mais curta e fácil ... Definitivamente, a que eu pessoalmente prefiro.
Nostradamus

35

Para o exemplo do OP:

s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"

Podemos usar o Yaml para lidar com esse tipo de json não padrão em string:

>>> import yaml
>>> s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> s
"{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> yaml.load(s)
{'muffin': 'lolz', 'foo': 'kitty'}

1
Isso fará com que 'sim' e 'não' cordas para ser convertido para Verdadeiro / Falso
Eric Marcos

23

Se a string sempre puder ser confiável, você poderá usar eval(ou usar literal_evalcomo sugerido; é seguro, não importa qual seja a string). Caso contrário, você precisará de um analisador. Um analisador JSON (como simplejson) funcionaria se ele apenas armazenasse conteúdo compatível com o esquema JSON.


8
A partir do 2.6, o simplejson é incluído na biblioteca padrão do Python como o módulo json.
Eli Courtwright

11
Sim, essa é uma boa resposta, mas observe que o JSON oficialmente não suporta sequências de citação simples, conforme fornecido no exemplo do pôster original.
Ben Hoyt

19

Use json. a astbiblioteca consome muita memória e mais devagar. Eu tenho um processo que precisa ler um arquivo de texto de 156Mb. Astcom atraso de 5 minutos para o dicionário de conversão jsone 1 minuto usando 60% menos memória!


13
mas tem seus limites: tente converter a string "{'foo': 'bar',}"
ntg 08/12/16

12

Para resumir:

import ast, yaml, json, timeit

descs=['short string','long string']
strings=['{"809001":2,"848545":2,"565828":1}','{"2979":1,"30581":1,"7296":1,"127256":1,"18803":2,"41619":1,"41312":1,"16837":1,"7253":1,"70075":1,"3453":1,"4126":1,"23599":1,"11465":3,"19172":1,"4019":1,"4775":1,"64225":1,"3235":2,"15593":1,"7528":1,"176840":1,"40022":1,"152854":1,"9878":1,"16156":1,"6512":1,"4138":1,"11090":1,"12259":1,"4934":1,"65581":1,"9747":2,"18290":1,"107981":1,"459762":1,"23177":1,"23246":1,"3591":1,"3671":1,"5767":1,"3930":1,"89507":2,"19293":1,"92797":1,"32444":2,"70089":1,"46549":1,"30988":1,"4613":1,"14042":1,"26298":1,"222972":1,"2982":1,"3932":1,"11134":1,"3084":1,"6516":1,"486617":1,"14475":2,"2127":1,"51359":1,"2662":1,"4121":1,"53848":2,"552967":1,"204081":1,"5675":2,"32433":1,"92448":1}']
funcs=[json.loads,eval,ast.literal_eval,yaml.load]

for  desc,string in zip(descs,strings):
    print('***',desc,'***')
    print('')
    for  func in funcs:
        print(func.__module__+' '+func.__name__+':')
        %timeit func(string)        
    print('')

Resultados:

*** short string ***

json loads:
4.47 µs ± 33.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
builtins eval:
24.1 µs ± 163 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
ast literal_eval:
30.4 µs ± 299 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
yaml load:
504 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

*** long string ***

json loads:
29.6 µs ± 230 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
builtins eval:
219 µs ± 3.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
ast literal_eval:
331 µs ± 1.89 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
yaml load:
9.02 ms ± 92.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Conclusão: prefira json.loads


5
Exceto que isso não funcionará com sua string de aspas simples, que fazia parte do seu problema inicial. O desempenho nunca foi mencionado.
Michael Campbell

1
Uau ... Super explicação ...
smack cherry

5
string = "{'server1':'value','server2':'value'}"

#Now removing { and }
s = string.replace("{" ,"")
finalstring = s.replace("}" , "")

#Splitting the string based on , we get key value pairs
list = finalstring.split(",")

dictionary ={}
for i in list:
    #Get Key Value pairs separately to store in dictionary
    keyvalue = i.split(":")

    #Replacing the single quotes in the leading.
    m= keyvalue[0].strip('\'')
    m = m.replace("\"", "")
    dictionary[m] = keyvalue[1].strip('"\'')

print dictionary

3
Muitos erros nessa abordagem. E se o valor de uma chave contiver {ou }. E se estiver aninhado dict. E se o valor contiver ,??
Om Sao

4

nenhuma libs é usada:

dict_format_string = "{'1':'one', '2' : 'two'}"
d = {}
elems  = filter(str.isalnum,dict_format_string.split("'"))
values = elems[1::2]
keys   = elems[0::2]
d.update(zip(keys,values))

NOTA: Como codificado permanentemente split("'"), funcionará apenas para cadeias de caracteres em que os dados são "entre aspas simples".

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.