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.

UberJumper
fonte
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
Jacob Gabrielson
fonte
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

0x539
fonte
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'>
tokhi
fonte
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'}
lqhcpsgbl
fonte
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.

Blixt
fonte
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!

Rogerio Silveira
fonte
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

Anatoly Alekseev
fonte
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
Siva Kameswara Rao Munipalle
fonte
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".

tamerlaha
fonte