Forum: PC-Programmierung Python: re Muster


von Daniel -. (root)


Lesenswert?

Muster ist wie folgt

abc<NL>
foo : bar<NL>

<NL> ist newline
1
pattern = re.compile(r'(?P<lhs>.*?)(\s+:\s+(?P<rhs>.*?))?')
2
m = pattern.match("hallo")
3
m is not None => True
4
m.group("lhs") => ''

lhs = left hand side
rhs = right hand side

da die linke Seite alles matchet (.*), darf : nicht konsumiert werden,
daher nongreedy (.*?)
so die rechte Seite ist optional, deswegen steht ihr Inhalt in ()?
da ich jedoch nicht in white spaces interessiert bin, erzeuge ich
neue benannte Gruppe rhs.
Irgendwo habe ich einen Gedankenfehler drin. Kann jemand mir helfen?

edit: ich will also beides matchen können

foo<NL>
foo : bar<NL>

von Klaus T. (gauchi)


Lesenswert?

Heisst nongreedy nicht, dass er abbricht sobald er einen Treffer hat?

(.*) passt auf einen leeren string
der hintere Teil ist wegen dem fehlenden ' : ' einfach leer, das ist 
auch zulässig. Folglich kriegst du '' für lhs zurück.

So glaube ich zumindest, dass das Ergebnis zustande kommt.

Ich würde dein Problem wahrscheinlich eher mit
1
string.split(' : ')
lösen, aber das erfordert natürlich noch ein paar Zeilen um überzählige 
Leerzeichen zu löschen und fehlende rechte Seiten abzufangen.

von Daniel -. (root)


Lesenswert?

1
pattern = re.compile(r'^(?P<lhs>.*?)(\s+:\s+(?P<rhs>.*?))?$')

löst das Problem! Hinzugekommen sind nur ^ und $.
Hmm, ^ ist glaube ich irrelevant. $ nicht. Ich verstehe nicht
so wirklich wieso RE-engine $ braucht. Ok, es steht für das
Ende der Zeile. Ende der Zeile ist doch normalerweise \n. (unix)
Oder \r\n (Windows). Vorher habe ich jedoch
1
pattern = re.compile(r'^(?P<lhs>.*?)(\s+:\s+(?P<rhs>.*?))?', re.DOTALL)
probiert. \n hätte doch in diesem Fall durch . gematched werden müssen.
Hat aber so nicht funktioniert.

Konsumiert $ eigentlich das Newline Zeichen? Im Falle von ^ gibt es
zum Beispiel kein reales Zeichen im Inputstream zu konsumieren.

von Klaus T. (gauchi)


Lesenswert?

Das funktioniert, weil die Kombination '^' ... '$' fordert, dass der 
ganze String auf das Muster passt.

von Daniel -. (root)


Lesenswert?

danke für den Tipp
vielleicht sollte ich doch sowas lieber nehmen
>
1
> map(string.strip, "foo : bar".split(":"))
2
>

von Daniel -. (root)


Lesenswert?

Klaus T. schrieb:
> Das funktioniert, weil die Kombination '^' ... '$' fordert, dass der
> ganze String auf das Muster passt.

ich glaube ich verstehe nicht Punkt für Punkt wie eine RE vorgeht.
Meine naive Annahme ist immer die - RE geht von links nach rechts vor
und match greedy. Wenn wir nongreedy von RE verlangen, dann wird es
tricky. Die RE muss nun schauen was im Pattern(nicht im Matchstring!)
weiter rechts steht. Damit wird das Vertilgen von Zeichen durch .
irgendwann eingestoppt, WENN die rechte Patternseite auf den Rest des
Matchstringes passt. Richtig soweit?
In meinem Fall gibt 2 nongreedy in Folge. Ich glaube mein Fehler war,
dass nach dem 2-ten nongreedy Matchall ich die notwendige rechte Seite
der RE Engine nicht gegeben habe. $ erfüllt genau diese Funktion.
Ich könnte aber ein beliebiges Zeichen benutzen. Beispielsweise

"foox"
"foo : barx"

Entspricht zwar nicht dem was ich matchen will, aber verifiziert
meine Hypothese :)

>>> p = re.compile(r'(?P<one>.*?)(\s+:\s+(?P<two>.*?))?x')
>>> m = p.match("hallo : foox")
>>> m.group("one")
'hallo'
>>> m.group("two")
'foo'
>>> m = p.match("hallox")
>>> m.group("two")
>>> m.group("one")
'hallo'

QED und halliluja :)

von Klaus T. (gauchi)


Lesenswert?

Guck Dir mal hier
 http://docs.python.org/library/re.html#regular-expression-syntax
 den *? Operator an, da steht was greedy und nongreedy bedeuten.

Prinzipiell sucht eine Regular Expression so lange, bis jeder Teil des 
Musters seine Entsprechung gefunden hat. Das bedeutet normal aber nicht, 
dass die gesamte Eingabe davon abgedeckt werden muss.

Dein Muster besteht aus zwei Teilen, die jeweils (fast) beliebige Länge 
haben dürfen:
1
(?P<lhs>.*?)
und
1
(\s+:\s+(?P<rhs>.*?))?
(Ok, der 2. Teil kann niemals aus einem oder zwei Zeichen bestehen, aber 
Null und >= 3 sind erlaubt.)

Wenn du sie auf non-greedy stellst, wird jedes dieser Muster versuchen, 
einen möglichst kurzen Teil in deiner Eingabe zu finden, so dass das 
Gesamtergebnis passt. Der kürzeste mögliche String, der hier erlaubt 
ist, ist leer.

Durch die '^' und '$' forderst du zusätzlich, dass dein Muster die 
gesamte Eingabe (bzw die ganze Zeile wenn es mehrere sind) abdeckt, also 
muss jedes Zeichen in der Eingabe auch eine Entsprechung im Muster 
finden, also sind 2 Strings der Länge 0 nicht mehr zulässig. Daher 
muss sich deine linke Seite so weit ausdehnen, dass die Rechte Seite 
den Rest übernehmen kann und das kann sie nur wenn ein ' : ' im String 
vorkommt.

Der '^' ist bei Python deshalb nicht notwendig, weil die Funktion match 
immer ab dem Beginn der Eingabe sucht. Das bedeutet, dass du immer ein 
implizites '^' vorne dran hast, wenn du match benutzt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.