Seite 1 von 2

String aufbröseln...

Verfasst: Mittwoch 9. Juli 2008, 13:02
von jens
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?

Verfasst: Mittwoch 9. Juli 2008, 13:09
von Karl
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.

Re: String aufbröseln...

Verfasst: Mittwoch 9. Juli 2008, 14:11
von Kurt Z
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 "=";

Verfasst: Mittwoch 9. Juli 2008, 14:19
von Pekh
Wie wäre es denn mit zwei splits, einmal " " und dann für alle Teilstrings noch einmal "=" ?

Verfasst: Mittwoch 9. Juli 2008, 14:23
von DasIch
Wenn man auf Leerzeichen splitet und einer der Werte ist ein String passiert was? ;)

Verfasst: Mittwoch 9. Juli 2008, 14:29
von Michael Schneider
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

Verfasst: Mittwoch 9. Juli 2008, 14:33
von Kurt Z
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.

Verfasst: Mittwoch 9. Juli 2008, 14:34
von Karl
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.

Verfasst: Mittwoch 9. Juli 2008, 14:37
von Masaru
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<<

Verfasst: Mittwoch 9. Juli 2008, 14:40
von 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.

Verfasst: Mittwoch 9. Juli 2008, 14:44
von Kurt Z
Darf ein String selbst wieder Quotes enthalten? : )

M.a.W. Soll es vollständiger Pythonsyntax sein?

Verfasst: Mittwoch 9. Juli 2008, 14:50
von Karl
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.

Verfasst: Mittwoch 9. Juli 2008, 15:16
von EnTeQuAk
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

Verfasst: Mittwoch 9. Juli 2008, 15:23
von Karl
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 :)

Verfasst: Mittwoch 9. Juli 2008, 15:25
von Masaru
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<<

Verfasst: Mittwoch 9. Juli 2008, 15:31
von EnTeQuAk
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

Shlex Grammatik?

Verfasst: Mittwoch 9. Juli 2008, 15:47
von Kurt Z
Frage: Wie parse ich mit Shlex einen String an der sowohl ' als auch " enthält ? Bzw. wie escape ich Quotes?

Verfasst: Mittwoch 9. Juli 2008, 15:50
von BlackJack
@Kurt Z: Mit einem Backslash.

Verfasst: Mittwoch 9. Juli 2008, 15:53
von Michael Schneider
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

Verfasst: Mittwoch 9. Juli 2008, 15:54
von Masaru
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<<