Dzielenie ciągu rozdzielanego średnikami do słownika w Pythonie

84

Mam ciąg, który wygląda tak:

"Name1=Value1;Name2=Value2;Name3=Value3"

Czy w Pythonie jest wbudowana klasa / funkcja, która pobierze ten ciąg i skonstruuje słownik, tak jakbym to zrobił:

dict = {
    "Name1": "Value1",
    "Name2": "Value2",
    "Name3": "Value3"
}

Przejrzałem dostępne moduły, ale nie mogę znaleźć niczego, co pasowałoby.


Dzięki, wiem, jak samemu stworzyć odpowiedni kod, ale ponieważ takie małe rozwiązania to zwykle pola minowe czekające na wystąpienie (np. Ktoś pisze: Name1 = 'Value1 = 2';) itd., To zwykle wolę trochę wcześniej sprawdzona funkcja.

Wtedy zrobię to sam.

Lasse V. Karlsen
źródło
czy twoje pytanie wymaga obsługi s = r'Name1='Value=2';Name2=Value2;Name3=Value3;Name4="Va\"lue;\n3"'danych wejściowych (uwaga: średnik w ciągu cytowanym, cudzysłów jest poprzedzony ukośnikiem odwrotnym, \nużywana jest ucieczka, używane są zarówno pojedyncze, jak i podwójne cudzysłowy)?
jfs
To moje pytanie ma ponad 6 lat, kod, który się z tym wiąże, już dawno został zastąpiony :) I nie, nie wymagało obsługi cytatów. Chciałem po prostu mieć gotową funkcję zamiast pisać coś samodzielnie. Jednak kodu już dawno nie ma.
Lasse V. Karlsen,

Odpowiedzi:

144

Nie ma wbudowanej funkcji, ale można to osiągnąć w dość prosty sposób za pomocą zrozumienia generatora:

s= "Name1=Value1;Name2=Value2;Name3=Value3"
dict(item.split("=") for item in s.split(";"))

[Edytuj] Ze swojej aktualizacji wskazałeś, że może zajść potrzeba obsługi cytowania. To komplikuje sprawę, w zależności od tego, jakiego dokładnie formatu szukasz (jakie znaki cudzysłowu są akceptowane, jakie znaki ucieczki itp.). Możesz spojrzeć na moduł csv, aby sprawdzić, czy może on pokryć twój format. Oto przykład: (Zauważ, że API jest trochę niezgrabne w tym przykładzie, ponieważ CSV jest zaprojektowany do iteracji przez sekwencję rekordów, stąd wywołania .next (), które wykonuję, aby spojrzeć na pierwszą linię. Dostosuj do do Twoich potrzeb):

>>> s = "Name1='Value=2';Name2=Value2;Name3=Value3"

>>> dict(csv.reader([item], delimiter='=', quotechar="'").next() 
         for item in csv.reader([s], delimiter=';', quotechar="'").next())

{'Name2': 'Value2', 'Name3': 'Value3', 'Name1': 'Value1=2'}

Jednak w zależności od dokładnej struktury twojego formatu może być konieczne napisanie własnego prostego parsera.

Brian
źródło
kod nie obsługuje cytowania, spróbuj: s = "Name1='Value;2';Name2=Value2;Name3=Value3"(uwaga: średnik w cytowanej Name1wartości).
jfs
1
Nie mam pojęcia, dlaczego drugi przykład rzuca AttributeError: '_csv.reader' object has no attribute 'next'mi się. Oczywiście, że tak import csv.
Youngjae
@Brian Czy istnieje sposób, aby zapisać wartości jako liczby całkowite, a nie jako ciąg?
ChasedByDeath
6

To jest bliskie zrobienia tego, co chciałeś:

>>> import urlparse
>>> urlparse.parse_qs("Name1=Value1;Name2=Value2;Name3=Value3")
{'Name2': ['Value2'], 'Name3': ['Value3'], 'Name1': ['Value1']}
Kyle Gibson
źródło
2
psuje się, jeśli jest &lub %na wejściu.
jfs
@jfs, ale ciąg nie zawiera żadnego z nich.
Vishal Singh
@VishalSingh: większość odwiedzających StackOverflow pochodzi z Google, dlatego odpowiedzi tutaj nie dotyczą tylko autora, który zadał pytanie. Jeśli przyszedłem tutaj, szukając sposobu parsowania "ciągu oddzielonego średnikami do słownika, w Pythonie", to moje ciągi mogą zawierać &lub %- przynajmniej warto wspomnieć, że odpowiedź nie działa dla takich ciągów.
jfs
3
s1 = "Name1=Value1;Name2=Value2;Name3=Value3"

dict(map(lambda x: x.split('='), s1.split(';')))
D. Om
źródło
1

Można to zrobić po prostu za pomocą łączenia łańcuchów i rozumienia listy

",".join(["%s=%s" % x for x in d.items()])

>>d = {'a':1, 'b':2}
>>','.join(['%s=%s'%x for x in d.items()])
>>'a=1,b=2'
vijay
źródło
-2
easytiger $ cat test.out test.py | sed 's/^/    /'
p_easytiger_quoting:1.84563302994
{'Name2': 'Value2', 'Name3': 'Value3', 'Name1': 'Value1'}
p_brian:2.30507516861
{'Name2': 'Value2', 'Name3': "'Value3'", 'Name1': 'Value1'}
p_kyle:7.22536420822
{'Name2': ['Value2'], 'Name3': ["'Value3'"], 'Name1': ['Value1']}
import timeit
import urlparse

s = "Name1=Value1;Name2=Value2;Name3='Value3'"

def p_easytiger_quoting(s):
    d = {}
    s = s.replace("'", "")
    for x in s.split(';'):
        k, v = x.split('=')
        d[k] = v
    return d


def p_brian(s):
    return dict(item.split("=") for item in s.split(";"))

def p_kyle(s):
    return urlparse.parse_qs(s)



print "p_easytiger_quoting:" + str(timeit.timeit(lambda: p_easytiger_quoting(s)))
print p_easytiger_quoting(s)


print "p_brian:" + str(timeit.timeit(lambda: p_brian(s)))
print p_brian(s)

print "p_kyle:" + str(timeit.timeit(lambda: p_kyle(s)))
print p_kyle(s)
łatwy tygrys
źródło
To nie odpowiada na pytanie, ponieważ nie obsługuje cytowania. Spróbuj s = "Name1='Value1=2';Name2=Value2" and csv` (jak w zaakceptowanej odpowiedzi Briana) lub parse_qs(jak w przypadku Kyle'a) da się to poprawnie, podczas gdy twój podniesie ValueError. OP wyraźnie mówi, że „takie małe rozwiązania to zwykle pola minowe czekające, aby się wydarzyć”, dlatego chce wbudowanego lub innego dobrze przetestowanego rozwiązania i podaje przykład, który złamie twój kod.
abarnert
Ach, tego nie widziałem. nadal. i tak byłoby szybsze niż wszystkie twoje rozwiązania przygotowanie tych w głównym łańcuchu przed iteracją i przywoływanie funkcji replace tysiące razy. Zaktualizuję
easytiger
Nie jestem pewien, jak zamierzasz to przygotować. Ale nawet jeśli to zrobisz, wydaje się, że jest to dokładnie to, czego OP obawiał się w prostym rozwiązaniu. Czy na pewno nie ma żadnych innych min przed nami? Czy możesz to udowodnić ku zadowoleniu OP?
abarnert
OK, teraz, kiedy widziałem twoją edycję… Po pierwsze, w s.replaceogóle nic nie robi; po prostu zwraca nowy ciąg, który zignorujesz. Po drugie, nawet jeśli zrobisz to dobrze ( s = s.replace…), to nie rozwiązuje problemu, po prostu dodaje nowy. Wypróbuj na moim przykładzie lub na OP.
abarnert
Specyfikacja wyraźnie obejmuje obsługę przykładowych danych wejściowych, o których wspomniał w swoim pytaniu Name='Value1=2';. Twój kod tego nie obsługuje. I nie jestem pewien, jak byś to odkażał bez analizowania go w jakiś sposób, który będzie tak samo powolny, jak urlparselub csvw pierwszej kolejności.
abarnert
-2

JEŚLI Twoja Wartość1, Wartość2 to tylko symbole zastępcze dla rzeczywistych wartości, możesz również użyć tej dict()funkcji w połączeniu z eval().

>>> s= "Name1=1;Name2=2;Name3='string'"
>>> print eval('dict('+s.replace(';',',')+')')
{'Name2: 2, 'Name3': 'string', 'Name1': 1}

Dzieje się tak, ponieważ dict()funkcja rozumie składniędict(Name1=1, Name2=2,Name3='string') . Spacje w ciągu znaków (np. Po każdym średniku) są ignorowane. Zwróć jednak uwagę, że wartości łańcuchowe wymagają cytowania.

Rabarberskiego
źródło
Dzięki, upvote string.replace działało dobrze. Nie wiem, dlaczego nie mogłem się rozdzielić. Zrobiłem i = textcontrol.GetValue () na tc box, a następnie o = i.split (';'), ale nie wyprowadziłem ciągu, tylko narzekałem na format, w przeciwieństwie do zamiany.
Iancovici
1
s.replace(';'rozwiązanie oparte ;na cudzysłowach nie działa, jeśli wewnątrz wartości znajduje się cudzysłów. eval jest zły i w tym przypadku jest niepotrzebny.
jfs