Strings zu Tuple über ( und )

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.
Antworten
nvidia
User
Beiträge: 31
Registriert: Freitag 11. Februar 2011, 16:46

Hallo,

Code: Alles auswählen

s = '("w1" "w2" ("w3" "w4") ("w5") "w6" ("w7"))'
wie kann ich diesen String zum Tuple an den "(" oder ")" transformieren?

//und noch eine frage
was macht die -1 da:

Code: Alles auswählen

index[1:-1]
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

nvidia hat geschrieben:Hallo,

Code: Alles auswählen

s = '("w1" "w2" ("w3" "w4") ("w5") "w6" ("w7"))'
wie kann ich diesen String zum Tuple an den "(" oder ")" transformieren?
Kommata nach Ausführungszeichen einfügen und dann in `eval` werfen zum Beispiel.
//und noch eine frage
was macht die -1 da:

Code: Alles auswählen

index[1:-1]
Das nach dem Doppelpunkt gibt das Ende des Slices an; ein negatives Indexwert steht in Python für "von hinten zählen".
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ad 1) Nun, `eval` existiert, dass solltest du allerdings nur einsetzen wenn du a) die Risiken kennst und b) ausschliessen kannst, dass sie ausgenutzt werden. Daneben gibt es noch das `ast`-Modul.

Ad 2) Teste es doch:

Code: Alles auswählen

In [1]: s = '("w1" "w2" ("w3" "w4") ("w5") "w6" ("w7"))'

In [2]: s[1:-1]
Out[2]: '"w1" "w2" ("w3" "w4") ("w5") "w6" ("w7")'
BlackJack

Eine Lösung mit PyParsing:

Code: Alles auswählen

from pyparsing import QuotedString, Forward, Group, Suppress, ZeroOrMore


def main():
    TUPLE = Forward()
    TUPLE << Group(
        Suppress('(') + ZeroOrMore(QuotedString('"') | TUPLE) + Suppress(')')
    ).setParseAction(lambda toks: tuple(toks[0]))
    ast = TUPLE.parseString('("w1" "w2" ("w3" "w4") ("w5") "w6" ("w7"))')
    print ast[0]


if __name__ == '__main__':
    main()
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Hier mal mein Vorschlag:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8

import re

TOKENS = re.compile(r'(?P<bl>\()|(?P<br>\))|(?P<data>\w+)|\s')


def tokenize(data):
    for token in re.finditer(TOKENS, data):
        yield token


def loads(data):
    stack = [[]]
    for token in tokenize(data):
        if token.group("bl"):
            stack.append(list())
        elif token.group("br"):
            tmp = tuple(stack.pop())
            stack[-1].append(tmp)
        elif token.group("data"):
            stack[-1].append(token.group())
        else:
            continue
    return stack.pop().pop()


if __name__ == "__main__":
    data = '("w1" "w2" ("w3" "w4") ("w5") "w6" ("w7"))'
    print loads(data)
Mir missfällt dabei noch, dass ich den Stack mit einer Dummy-Liste initialisieren muss. Das finde ich spezielle am Schluss dann unschön (2x pop).

Ich vermute mal sma und BlackJack zaubern da bessere Lösungen aus dem Hut :-)

Edit: Wußt ich's doch :-D
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Hyperion: Ich habe auch mal eine `re`-Lösung gebastelt, für die komplette Syntax aus dem anderen Thread mit NIL und Zahlen:

Code: Alles auswählen

import re
from pprint import pprint

SOURCE = '''\
(
   ("TEXT" "PLAIN" ("CHARSET" "utf-8") NIL NIL "QUOTED-PRINTABLE" 871 27 NIL NIL NIL)
   (
      ("TEXT" "HTML" ("CHARSET" "utf-8") NIL NIL "QUOTED-PRINTABLE" 2275 29 NIL NIL NIL)
      ("APPLICATION" "VND.OASIS.OPENDOCUMENT.TEXT" ("NAME" "Ethische Problemfelder.odt") NIL NIL "BASE64" 875794 NIL ("ATTACHMENT" ("FILENAME" "Ethische Problemfelder.odt")) NIL)
      ("TEXT" "HTML" ("CHARSET" "utf-8") NIL NIL "7BIT" 92 0 NIL NIL NIL) "MIXED" ("BOUNDARY" "Apple-Mail-4-784936864") NIL NIL
   ) "ALTERNATIVE" ("BOUNDARY" "Apple-Mail-3-784936862") NIL NIL
)'''


def pattern_dispatch(patterns_and_actions, source):
    regex = re.compile('|'.join('(%s)' % r for r, _ in patterns_and_actions))
    actions = [a for _, a in patterns_and_actions]
    i = 0
    for match in iter(lambda: regex.match(source, i), None):
        actions[match.lastindex - 1](match.group())
        i = match.end()
    return i


def parse(source):
    stack = [[]]
    pattern_dispatch(
        [
            (r'\(', lambda _: stack.append(list())),
            (r'\)', lambda _: stack[-2].append(tuple(stack.pop()))),
            (r'NIL', lambda _: stack[-1].append(None)),
            (r'[0-9]+', lambda t: stack[-1].append(int(t))),
            (r'"[^"]*"', lambda t: stack[-1].append(t[1:-1])),
            (r'\s+', lambda _: None),
        ],
        source
    )
    return stack[0][0]


def main():
    pprint(parse(SOURCE))
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Die Idee, die Pattern mit den Funktionen zu kombinieren gefällt mir da sehr gut! Ich hatte da auch überlegt, bin aber auf die nahe liegende Lösung (den RegExp erst später komplett zusammenzubauen) nicht gekommen. Damit wird der auch leichter wartbar bzw. überschaubar. Ich habe da auch schon irgend wie an den Weg aus pygments gedacht, wie die Lexer konstruieren, aber danke für Deinen Code, der mir da jetzt die Augen öffnet :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten