Czy konwertować ciąg reprezentujący słownik na słownik?


767

Jak przekonwertować strreprezentację a dict, na przykład następującego ciągu, na a dict?

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

Wolę nie używać eval. Czego jeszcze mogę użyć?

Głównym tego powodem jest jedna z moich klas współpracowników, którą napisał, konwertuje wszystkie dane wejściowe na ciągi. Nie jestem w nastroju, aby iść i modyfikować jego zajęcia, aby rozwiązać ten problem.


1
Jeśli nie możesz używać Pythona 2.6, możesz zastosować prostą bezpieczną implementację, taką jak code.activestate.com/recipes/364469 Jest to piggyback na kompilatorze Python, dzięki czemu nie musisz wykonywać całej ciężkiej pracy sam.
Ned Batchelder,

11
Uwaga : dla tych, którzy przychodzą tutaj ze zwodniczo podobnymi danymi JSON , zamiast tego należy przeczytać Parse JSON w Pythonie . JSON to nie to samo, co Python . Jeśli masz "podwójne cudzysłowy wokół swoich ciągów, prawdopodobnie masz dane JSON. Możesz także poszukać null, truelub false, używa składni Python None, Truei False.
Martijn Pieters

Odpowiedzi:


1167

Począwszy od Python 2.6, możesz użyć wbudowanego ast.literal_eval:

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

Jest to bezpieczniejsze niż używanie eval. Jak mówią własne dokumenty:

>>> pomoc (ast.literal_eval)
Pomoc na temat funkcji literal_eval w module ast:

literal_eval (node_or_string)
    Bezpiecznie oceń węzeł wyrażenia lub ciąg znaków zawierający Python
    wyrażenie. Podany ciąg lub węzeł może składać się tylko z następujących elementów
    Struktury dosłowne w języku Python: ciągi, liczby, krotki, listy, dykta, booleany,
    i brak.

Na przykład:

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

Powinienem dodać, że musisz zdezynfekować ciąg do użycia z ast.literal_eval. (upewnij się, że cudzysłowy / podwójne cudzysłowy w łańcuchu są unikane)
Paulo Matos

pojawia się ten błąd Jestem na Pythonie 2.6 (x86) na Windowsie 7 x64 Plik „D: \ Python26 \ lib \ ast.py”, wiersz 48, w literale_eval node_or_string = parsuj (node_or_string, mode = 'eval') Plik "D : \ Python26 \ lib \ ast.py ", wiersz 36, w kompilacji ze składnikiem zwrotnym (wyrażenie, nazwa pliku, tryb, PyCF_ONLY_AST) Plik„ <unknown> ”, wiersz 1 ^

co z "dict(a=1)"ciągami stylów?
n611x007

Wydaje się, że nie działa to w przypadku wartości wyliczeniowej w słowniku. Np .: d = "{'col': <Colors.RED: 2>, 'val': 2}"
shivshnkr

3
dlaczego nie używamy json.dumps i json.loads insead, znalazłem to rozwiązanie bardziej zaawansowane niż eval
Auros132

232

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

JSON może rozwiązać ten problem, chociaż jego dekoder chce podwójnych cudzysłowów wokół kluczy i wartości. Jeśli nie masz nic przeciwko zamianie hacka ...

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

UWAGA: jeśli masz pojedyncze cudzysłowy jako część swoich kluczy lub wartości, to się nie powiedzie z powodu niewłaściwej zamiany znaków. To rozwiązanie jest zalecane tylko, jeśli masz silną awersję do rozwiązania ewaluacyjnego.

Więcej informacji o pojedynczym cytacie json: jQuery.parseJSON zgłasza błąd „Nieprawidłowy JSON” z powodu zmiany pojedynczego cytatu w JSON


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

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

1
Szukałem tego rozwiązania. +1za poinformowanie, że dekoder chce podwójnych cudzysłowów wokół kluczy i wartości.
h8pathak

Kolejny problem dotyczy "{0: 'Hello'}".
Finn Årup Nielsen

3
Nie powiedzie się to również, jeśli masz przecinki końcowe (niezgodne z JSON), np .: „{„ muffin ”:„ lolz ”,„ foo ”:„ kitty ”,}”
guival

159

za pomocą 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
Nie sądzę, że odpowiada na odpowiedź PO. Jak używamy json.laads do konwersji ciągu s = "{'muffin': 'lolz', 'foo': 'kitty'}” do dyktowania?
technazi

dlaczego to drukowanie „u” na wyjściu? np. - str = '{"1": "P", "2": "N", "3": "M"}' d = json.loads (str) print d wyjście to: {u'1 ': u'P ', u'3': u'M ', u'2': u'N '}
user905

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

Istnieją jednak ograniczenia, np .: h = „{muffin”: „lolz”, „foo”: „kitty”,} ”, a także h =„ {„muffin's”: „lolz”, „foo”: „kitty „}”, (właśnie zauważyłem część tych samych komentarzy w podobnej odpowiedzi ... wciąż zostawiam tutaj dla kompletności ...)
ntg

4
Moim zdaniem jest to najkrótszy i najłatwiejszy sposób ... Zdecydowanie ten, który osobiście wolę.
nostradamus,

35

Do przykładu OP:

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

Możemy używać Yamla do radzenia sobie z tego rodzaju niestandardowymi plikami Json w łańcuchu:

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

1
Spowoduje to, że ciągi „tak” i „nie” zostaną przekonwertowane na True / False
Eric Marcos

23

Jeśli ciągowi zawsze można zaufać, możesz użyć eval(lub użyć literal_evalzgodnie z sugestią; jest bezpieczny bez względu na ciąg.) W przeciwnym razie potrzebujesz parsera. Parser JSON (taki jak simplejson) działałby, gdyby kiedykolwiek przechowywał tylko zawartość zgodną ze schematem JSON.


8
Począwszy od wersji 2.6, simplejson jest zawarty w standardowej bibliotece Pythona jako moduł json.
Eli Courtwright,

11
Tak, to dobra odpowiedź, ale zauważ, że oficjalnie JSON nie obsługuje ciągów pojedynczych cudzysłowów, jak podano w przykładzie oryginalnego plakatu.
Ben Hoyt

19

Zastosowanie json. astbiblioteka zużywa dużo pamięci i i wolniejsze. Mam proces, który musi odczytać plik tekstowy 156Mb. Astz 5-minutowym opóźnieniem dla słownika konwersji jsoni 1 minutą przy zużyciu 60% mniej pamięci!


13
ale ma swoje ograniczenia: spróbuj przekonwertować ciąg znaków {{foo ':' bar ',} "
ntg

12

Podsumowując:

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

Wyniki:

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

Wniosek: wolę json.loads


5
Tyle że to nie zadziała z jego pojedynczym ciągiem, który był częścią jego początkowego problemu. Wydajność nigdy nie została wspomniana.
Michael Campbell

1
Wow .... Super Wyjaśnienie ....
uderzenie wiśni

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
Wiele błędów w tym podejściu. Co jeśli wartość klucza zawiera {lub }. Co jeśli jest zagnieżdżony dict. Co jeśli wartość zawiera ,?
Om Sao

4

nie są używane żadne biblioteki lib:

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

UWAGA: Ponieważ jest zakodowany na stałe, split("'")będzie działał tylko dla ciągów, w których dane są „pojedynczymi cudzysłowami”.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.