String aufbröseln...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich habe eine String, der könnte so aussehen:
a=1 b="X Y Z" c=True d=None
Ich brauche das ganze als dict:

Code: Alles auswählen

{"a": 1, "b":"X Y Z", "c": True, "d": None}
Irgendeine Idee wie man das Umformen könnte? Evtl. mit RE?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Ja, Regex wäre da ganz praktisch.
Theoretisch reicht auch s.split('='), aber dann steht immer der Variablenname der nächsten zuweisung noch im Wert der vorherigen Zuweisung.

Code: Alles auswählen

['a', '1 b', '"X Y Z" c', 'True d', 'None']
Aber das wäre ja auch kein Problem, wenn deine Zuweisungen immer so bleiben ;) Nur wenn du dann in deinen Zuweisungen zum Beispiel einen String mit einem '=' zuweist, wird's ein Problem.

Edit: Ich seh grad, du bist Moderator mit 5000 Beiträgen :shock: Dann hab ich das Problem wohl unterschätzt :)
Aber ich denke trotzdem, dass das mit einem nicht allzu komplexem regulärem Ausdruck möglich ist.
Vielleicht gibt's dann Probleme bei verschachtelten Listen und so weiter, falls sowas auch in deinem String vorkommen kann.
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

jens hat geschrieben:Ich habe eine String, der könnte so aussehen:
a=1 b="X Y Z" c=True d=None
Ich brauche das ganze als dict:

Code: Alles auswählen

{"a": 1, "b":"X Y Z", "c": True, "d": None}
Irgendeine Idee wie man das Umformen könnte? Evtl. mit RE?
Kennt Python evaluierende RegExes wie bei perl mit dem e-Modifier also s/Pattern/Replacement/e?

Dann könntest du per RegEx jedes Paar um das = einzeln greifen, und in eine Zuwesiung packen die evaluiert wird.

EDIT: Ansonsten split whitespace und dann in der inneren schleife split "=";
Zuletzt geändert von Kurt Z am Mittwoch 9. Juli 2008, 14:21, insgesamt 1-mal geändert.
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Wie wäre es denn mit zwei splits, einmal " " und dann für alle Teilstrings noch einmal "=" ?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Wenn man auf Leerzeichen splitet und einer der Werte ist ein String passiert was? ;)
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hallo Jens,

so eine Frage von Dir? Das ist doch bestimmt ein Test, oder? ;-)

@Pekh: so einfach ist das nicht, weil Du auch die single-/double-quotes berücksichtigen musst. Der String "X Y Z" z.B. enthält auch Spaces, darf aber nicht getrennt werden.

Ich habe mal sowas zusammengezimmert:

Code: Alles auswählen

>>> s = '''a=1 b="X Y Z" c=True d='test' e=None'''
>>> cre = re.compile(r'''(?P<name>\w+)=(?P<quote>["']?)(?P<value>.+?)(?P=quote)(?=\s|$)''')
>>> cre.findall(s)
[('a', '', '1'), ('b', '"', 'X Y Z'), ('c', '', 'True'), ('d', "'", 'test'), ('e', '', 'None')]
>>> cre.search(s).groupdict()
{'name': 'a', 'value': '1', 'quote': ''}
Edit: unter Umständen must Du noch mit den Optionen wie re.DOTALL spielen, um auch zitierte lf zu respektieren
Der Ausdruck unterscheidet auch zwischen single- und double-quotes und gibt sie über den entsprechenden Dictionaryeintrag aus. So kannst Du im nachhinein erkennen, ob und was für ein String gegeben war.

Noch Fragen zum re-Ausdruck? :-)

Grüße,
Michel
Zuletzt geändert von Michael Schneider am Mittwoch 9. Juli 2008, 15:59, insgesamt 1-mal geändert.
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

DasIch hat geschrieben:Wenn man auf Leerzeichen splitet und einer der Werte ist ein String passiert was? ;)
murks pasiert! : )

Aber wenn Klammernpaare geparst werden müssen wird die RegEx so kompliziert, dass man lieber nach nem fertigen Parser sucht. ( Allerdings eine Regex die gleich nach dem = ein ' oder " fängt das sich am Ende wiederholt ginge grad noch so.)

Wenn in dem String valider Python-Code steckt, würde sich ja sowas wie eval anbieten, könnte aber bei Python Einrückungssyntax haarig werden...

Grundsätzlich wärs IMHO cleverer gleich sowas wie YAML oder JSON zu nehmen.
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Man kann das ganze so angehen:
Man geht bis zum 1. '=' (was ohne Probleme geht, bis dahin gibt's keine Strings) und untersucht dann mit ein paar If's, was für ein Typ der Variable zugewiesen wird. Erkennt man am 1. Zeichen
Bei Listenähnlichem sucht man eben nach dem passenden End-Zeichen ) ] } ..., geht mit einer Schleife ganz gut.
Bei Strings nach dem 2. " bzw '
Bei allen anderen bis zum Leerzeichen.
Müsste eigentlich alles enthalten, oder?
Wenn man will kann man das auch Teilweise mit regulären Ausdrücken machen, aber nötig ist es nicht unbedingt.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Hier mal meine Lösung:

Code: Alles auswählen

import re

splitRE = re.compile('(\w*="[\w\s=]*"|\w=\w*)')
PAIR_SEP = '='
NAT_TYPE_MAP = {
    'True'  : True,
    'False' : False,
    'None'  : None,
    }

def str2type(basestr):
    """Converts a string into matching type"""
    assert isinstance(basestr, (str, unicode))
    if NAT_TYPE_MAP.has_key(basestr):
        return NAT_TYPE_MAP[basestr]
    try:
        return int(basestr)
    except ValueError:
        pass
    return basestr

def str2dict(basestr):
    """Converts a string configuration into a dictionary"""
    assert isinstance(basestr, (str, unicode))
    result = {}
    for hit in splitRE.findall(basestr):
        key, value = hit.split(PAIR_SEP, 1)
        if result.has_key(key):
            raise KeyError('Duplicated key', key)
        result[key] = str2type(value)
    return result

Code: Alles auswählen

>>> for key, value in str2dict('a=1 b="X Y Z" c=True d=None').iteritems():
...     print key, value, type(value)
a 1 <type 'int'>
c True <type 'bool'>
b "X Y Z" <type 'str'>
d None <type 'NoneType'>
Beliebig typ-erweiterbar ;).

>>Masaru<<
Zuletzt geändert von Masaru am Mittwoch 9. Juli 2008, 14:54, insgesamt 1-mal geändert.
BlackJack

Vielleicht hilft das Modul `shlex` aus der Standardbibliothek einen Schritt weiter?

Code: Alles auswählen

In [84]: shlex.split('''a=1 b="X Y Z" c=True d='test' e=None''')
Out[84]: ['a=1', 'b=X Y Z', 'c=True', 'd=test', 'e=None']
An das Ergebnis kann man dann mit `split('=', 1)` gehen um die Bezeichner von den Werten zu trennen.

Ansonsten würde ich zu Pyparsing greifen, bevor ich anfangen würde unlesbare regex-Monster zu basteln.
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Darf ein String selbst wieder Quotes enthalten? : )

M.a.W. Soll es vollständiger Pythonsyntax sein?
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

BlackJack hat geschrieben:Vielleicht hilft das Modul `shlex` aus der Standardbibliothek einen Schritt weiter?

Code: Alles auswählen

In [84]: shlex.split('''a=1 b="X Y Z" c=True d='test' e=None''')
Out[84]: ['a=1', 'b=X Y Z', 'c=True', 'd=test', 'e=None']
An das Ergebnis kann man dann mit `split('=', 1)` gehen um die Bezeichner von den Werten zu trennen.

Ansonsten würde ich zu Pyparsing greifen, bevor ich anfangen würde unlesbare regex-Monster zu basteln.
shlex muss ich mir merken :)
Das hat mal so ganz nebenbei das Hauptproblem gelöst. Ist ja dann wirklich einfach.
Edit: Nur verschachtelte Listen oder sonstwas, was Leerzeichen außerhalb eines Strings enthalten darf, ist problematisch, wenn's eben Leerzeichen gibt.
EnTeQuAk
User
Beiträge: 986
Registriert: Freitag 21. Juli 2006, 15:03
Wohnort: Berlin
Kontaktdaten:

Die Lösung mit shlex scheint wohl die eleganteste zu sein. Ansonsten habe ich noch sowas anzubieten:

Code: Alles auswählen

ts = u'a=1 b="X Y Z" c=True d=None'

dd = {}

keys = [k for k in ts if ts[ts.index(k)+1]=='=']
for i, item in enumerate(ts):
    if item == '=':
        key = ts[i-1]
        last_key = keys[-1] == key
        if last_key:
            value = ts[i+1:]
        else:
            value = ts[i+1:ts.index(keys[keys.index(key)+1])-1]
        dd[key] = eval(value)
Gibt exakt das aus, was du magst :)

Oder eben

Code: Alles auswählen

In [196]: dict((k, v) for k,v in [x.split('=') for x in shlex.split(ts)])
Out[196]: {'a': '1', 'b': 'X Y Z', 'c': 'True', 'd': 'None'}
Gibt aber nicht exakt das aus, was du möchtest. `v` kannst du micht mit `eval` bearbeiten, da die value von `b` kein richtiger String ist... leider – da bekommst nur nen SyntaxError :(

Gruß, Christopher
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

EnTeQuAk: Deine Lösung kann aber zum Beispiel nicht mit einem = in einem String umgehen, weil dann ein SyntaxError kommen müsste.
Außerdem wird ja immer gegen eval gepredigt :)
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Nicht schlecht ... das hat mich auf folgende Idee gebracht:

Code: Alles auswählen

>>> import re
>>> splitRE = re.compile('(\w*=".*"|\w*=\w*)') 
>>> for hit in splitRE.findall(u'a=1 b="X=Y=Z" c=True d=None'):
... 	key, value = hit.split('=',1)
...     value = eval(value)
... 	print key, value, type(value)
... 	
a 1 <type 'int'>
b X=Y=Z <type 'str'>
c True <type 'bool'>
d None <type 'NoneType'>
@Karl: Hier wäre dann das Problem mit einem '=' im String wieder ausgehebelt ;).

>>Masaru<<
Zuletzt geändert von Masaru am Mittwoch 9. Juli 2008, 15:53, insgesamt 3-mal geändert.
EnTeQuAk
User
Beiträge: 986
Registriert: Freitag 21. Juli 2006, 15:03
Wohnort: Berlin
Kontaktdaten:

Karl hat geschrieben:EnTeQuAk: Deine Lösung kann aber zum Beispiel nicht mit einem = in einem String umgehen, weil dann ein SyntaxError kommen müsste.
Außerdem wird ja immer gegen eval gepredigt :)
Stimmt, das hier geht aber:

Code: Alles auswählen


def split(s):
    keys = [k for k in s if s[s.index(k)+1]=='=']
    for i, item in enumerate(s):
        if item == '=':
            key = s[i-1]
            if not key in keys:
                continue
            last_key = keys[-1] == key
            if last_key:
                value = s[i+1:]
            else:
                value = s[i+1:s.index(keys[keys.index(key)+1])-1]
            yield key, value

def main():
    ts = u'a=1 b="X =Y Z" c=True d=None'
    print dict((k, eval(v)) for k,v in split(ts))

if __name__ == '__main__':
    main()
Ansonsten sieht Masaru's Lösung auch nicht schlecht aus :D


€dit: Mist, geht auch nicht. Wenn X=Y im `b` value steht, gehts auch nicht. Vergesst die Lösung :D
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Frage: Wie parse ich mit Shlex einen String an der sowohl ' als auch " enthält ? Bzw. wie escape ich Quotes?
BlackJack

@Kurt Z: Mit einem Backslash.
Benutzeravatar
Michael Schneider
User
Beiträge: 569
Registriert: Samstag 8. April 2006, 12:31
Wohnort: Brandenburg

Hi Masaru,
Masaru hat geschrieben:@Karl: Hier wäre dann das Problem mit einem '=' im String wieder ausgehebelt ;).
Und was ist mit nicht-\w-Zeichen wie [.,;:], Klammern, usw.?

Jens hat bislang noch nicht behauptet (oder ich habe es überlesen), dass es sich um Programmcode oder sogar Python-Code handelt.

Michel
Diese Nachricht zersört sich in 5 Sekunden selbst ...
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Huch, hab den Seiten-Wechsel gar nicht mitbekommen beim editieren.

Naja, hier mal eine gaaanz kurze Variante

Code: Alles auswählen

import re 
s = u'a=1 b="X=Y=Z" c=True d=None'
r = dict([ (t[0], eval(t[1])) for t in [ h.split('=', 1) for h in re.findall('(\w*=".*"|\w*=\w*)', s) ]])
>>Masaru<<
Antworten