Convertir une représentation String d'un dictionnaire en dictionnaire?

768

Comment puis-je convertir la strreprésentation d'un dict, comme la chaîne suivante, en un dict?

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

Je préfère ne pas l'utiliser eval. Que puis-je utiliser d'autre?

La principale raison à cela, est l'une de mes classes de collègues qu'il a écrite, convertit toutes les entrées en chaînes. Je ne suis pas d'humeur à aller modifier ses cours, pour faire face à ce problème.

UberJumper
la source
1
Si vous ne pouvez pas utiliser Python 2.6, vous pouvez utiliser une implémentation simple de niveau de sécurité comme code.activestate.com/recipes/364469 Il se superpose au compilateur Python afin que vous n'ayez pas à faire tout le travail brut vous-même.
Ned Batchelder
11
Remarque : Pour ceux qui viennent ici avec des données JSON d' aspect trompeusement similaire , vous souhaitez plutôt lire Parse JSON en Python . JSON n'est pas la même chose que Python . Si vous avez "des guillemets autour de vos chaînes, vous avez probablement des données JSON. Vous pouvez également rechercher null, trueou false, les utilisations de la syntaxe Python None, Trueet False.
Martijn Pieters

Réponses:

1167

À partir de Python 2.6, vous pouvez utiliser le intégré ast.literal_eval:

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

C'est plus sûr que d'utiliser eval. Comme le disent ses propres documents:

>>> aide (ast.literal_eval)
Aide sur la fonction literal_eval dans le module ast:

literal_eval (node_or_string)
    Évaluez en toute sécurité un nœud d'expression ou une chaîne contenant un Python
    expression. La chaîne ou le nœud fourni ne peut comprendre que les éléments suivants
    Structures littérales Python: chaînes, nombres, tuples, listes, dict, booléens,
    et aucun.

Par exemple:

>>> 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
la source
Je dois ajouter que vous devez nettoyer la chaîne à utiliser avec ast.literal_eval. (assurez-vous que les guillemets / guillemets doubles dans la chaîne sont échappés)
Paulo Matos
je reçois cette erreur Je suis sur python 2.6 (x86) sur Windows 7 x64 Fichier "D: \ Python26 \ lib \ ast.py", ligne 48, dans literal_eval node_or_string = parse (node_or_string, mode = 'eval') File "D : \ Python26 \ lib \ ast.py ", ligne 36, en analyse retour compiler (expr, nom de fichier, mode, PyCF_ONLY_AST) Fichier" <unknown> ", ligne 1 ^ SyntaxError: syntaxe invalide
qu'en est-il "dict(a=1)"des cordes de style?
n611x007
Cela ne semble pas fonctionner pour la valeur enum dans un dictionnaire. Par exemple: d = "{'col': <Colours.RED: 2>, 'val': 2}"
shivshnkr
3
pourquoi ne pas utiliser json.dumps et json.loads insead, j'ai trouvé cette solution plus élevée que celle utilisant eval
Auros132
232

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

JSON peut résoudre ce problème bien que son décodeur veuille des guillemets doubles autour des clés et des valeurs. Si cela ne vous dérange pas de remplacer un hack ...

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'}

REMARQUE: si vous avez des guillemets simples dans le cadre de vos clés ou valeurs, cela échouera en raison d'un remplacement de caractères incorrect. Cette solution n'est recommandée que si vous avez une forte aversion pour la solution eval.

En savoir plus sur le guillemet simple json: jQuery.parseJSON génère une erreur "JSON invalide" en raison d'un guillemet simple échappé dans JSON

0x539
la source
12
{"foo": "b'ar"}
Mark E. Haase
4
{'foo': (1, 2, 3)}
Mark E. Haase
1
Je cherchais cette solution. +1pour informer que le décodeur veut des guillemets doubles autour des clés et des valeurs.
h8pathak
Un autre problème est pour "{0: 'Hello'}".
Finn Årup Nielsen
3
Cela échoue également si vous avez des virgules de fin (non conformes à JSON), par exemple: "{'muffin': 'lolz', 'foo': 'kitty',}"
guival
159

utilisant 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
la source
13
Je ne pense pas que cela réponde à la réponse du PO. Comment utiliser json.laads pour convertir une chaîne s = "{'muffin': 'lolz', 'foo': 'kitty'}" en dict?
technazi
pourquoi cette impression "u" dans la sortie ?? par exemple - str = '{"1": "P", "2": "N", "3": "M"}' d = json.loads (str) imprimer la sortie d est: {u'1 ': u'P ', u'3': u'M ', u'2': u'N '}
user905
2
@technazi: json.loads (h.replace ("'",' "'))
ntg
Cependant, il y a des limites, par exemple: h = '{"muffin": "lolz", "foo": "kitty",}', également h = '{"muffin's": "lolz", "foo": "kitty "} ', (vient de remarquer une partie des mêmes commentaires dans une réponse similaire ... laissant toujours ici pour être complet ...)
ntg
4
À mon avis, c'est le moyen le plus court et le plus simple ... Certainement celui que je préfère personnellement.
nostradamus
35

À l'exemple d'OP:

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

Nous pouvons utiliser Yaml pour gérer ce type de json non standard en chaîne:

>>> import yaml
>>> s = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> s
"{'muffin' : 'lolz', 'foo' : 'kitty'}"
>>> yaml.load(s)
{'muffin': 'lolz', 'foo': 'kitty'}
lqhcpsgbl
la source
1
Cela entraînera la conversion des chaînes «oui» et «non» en Vrai / Faux
Eric Marcos
23

Si la chaîne peut toujours être fiable, vous pouvez utiliser eval(ou utiliser literal_evalcomme suggéré; c'est sûr quelle que soit la chaîne.) Sinon, vous avez besoin d'un analyseur. Un analyseur JSON (tel que simplejson) fonctionnerait s'il ne stocke que du contenu qui correspond au schéma JSON.

Blixt
la source
8
Depuis 2.6, simplejson est inclus dans la bibliothèque standard Python en tant que module json.
Eli Courtwright
11
Oui, c'est une bonne réponse, mais notez que JSON ne prend pas officiellement en charge les chaînes entre guillemets simples, comme indiqué dans l'exemple de l'affiche originale.
Ben Hoyt
19

Utilisez json. la astbibliothèque consomme beaucoup de mémoire et et plus lentement. J'ai un processus qui doit lire un fichier texte de 156 Mo. Astavec 5 minutes de retard pour le dictionnaire de conversion jsonet 1 minutes en utilisant 60% de mémoire en moins!

Rogerio Silveira
la source
13
mais a ses limites: essayez de convertir la chaîne "{'foo': 'bar',}"
ntg
12

Résumer:

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('')

Résultats:

*** 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)

Conclusion: préférez json.loads

Anatoly Alekseev
la source
5
Sauf que cela ne fonctionnera pas avec sa chaîne entre guillemets simples, qui faisait partie de son problème initial. La performance n'a jamais été mentionnée.
Michael Campbell
1
Wow .... Super Explication ....
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
la source
3
Beaucoup d'erreurs dans cette approche. Que faire si la valeur d'une clé contient {ou }. Et s'il est imbriqué dict. Et si la valeur contient ,??
Om Sao
4

aucune bibliothèque n'est utilisée:

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))

REMARQUE: comme il a été codé split("'")en dur, il ne fonctionnera que pour les chaînes où les données sont entre guillemets simples.

tamerlaha
la source