Regex: Wann wiederholt sich das Matchen und wann nicht?

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
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht sollte die Frage auch anders lauten. Mein Corpus Delicti ist jedenfalls dieser String:

Code: Alles auswählen

>>> s = 'foo [ bar [ baz [ spam'
Das erste Pattern, was ich für diesen Fall genommen habe, sucht nach beliebigen sich wiederholenden Zeichen bis zur jeweils folgenden eckigen Klammer. Es funktioniert auch so, wie ich das erwartet hätte:

Code: Alles auswählen

>>> re.findall(r'(.*?)\[', s)
['foo ', ' bar ', ' baz ']
`spam` wird natürlich nicht gefunden, weil keine Klammer folgt.

Nun zum zweiten Pattern. Suche nach String, der mit eckiger Klammer beginnt und stoppe vor der nächsten Klammer:

Code: Alles auswählen

>>> re.findall(r'(\[.*?)\[', s)
['[ bar ']
Das Suchergebnis ist soweit in Ordnung. Die erste Klammer habe ich bewusst mit reingenommen. Was ich aber nicht verstehe: Warum findet der hier nur ein Ergebnis und hört dann auf? Was macht das zweite Pattern soviel anders als das erste? Ist doch einfach so, dass diesmal ein bestimmtes Zeichen vor dem Platzhalter steht und diese Kombination kommt ja wohl danach auch noch vor, oder nicht? Meine Vermutung wäre höchstens noch, dass es vielleicht was mit greedy und non-greedy zu tun hat, aber so richtig kann ich mir aus der Begründung keinen Reim machen. :(
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hab's jetzt mit Lookahead hinbekommen:

Code: Alles auswählen

>>> re.findall(r'\[.*?(?=\[)', s)
['[ bar ', '[ baz ']
Ich kann es mir jetzt nur so erklären, dass der "Such-Cursor" durch mein vorheriges Konstrukt vermutlich schon an der nachfolgenden Klammer vorbeigesprungen ist, weil diese für den ersten Treffer gebraucht und damit sozusagen "überschritten" wurde. Wo ist eigentlich sma, wenn man ihn mal braucht? :evil:

€dit: Ein entsprechend angepasster Versuch bestätigt meine Theorie:

Code: Alles auswählen

>>> s = 'foo [ bar [ baz [ spam [ ham'
>>> re.findall(r'(\[.*?)\[', s)
['[ bar ', '[ spam ']
Der Cursor befindet sich also nach dem ersten Treffer genau hinter der zweiten Klammer und kommt auch nicht mehr zurück. Daher sieht die Engine erst wieder die Klammer bei dem Teil mit `spam` und matcht dementsprechend.

Beim allerersten Pattern hat's daher gepasst, weil der Cursor ja in dem Moment genau an der letztmöglichen Stelle zum Matchen war. :)
Zuletzt geändert von snafu am Sonntag 10. Oktober 2010, 09:54, insgesamt 1-mal geändert.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Beim zweiten Fall wird nur "[ bar " gefunden, weil nach dem ersten Treffer nur noch " baz [ spam" zum Durchsuchen da ist und da ist offensichtlich kein Teilstring zu finden, der mit einem [ beginnt und mit einem [ endet. Du kannst nach einem Text suchen, ohne ihn zu "konsumieren", sodass er für die weitere Suche zur Verfügung steht, indem du am Ende "(?=\[)" benutzt. Soll der Text mit einem [ oder dem Textende enden, kannst du auch "(?=\[|$)" benutzen.

Stefan

Edit: SMA war nur eine Minuten zu spät - oder du zu ungeduldig...
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

sma hat geschrieben:Edit: SMA war nur eine Minuten zu spät - oder du zu ungeduldig...
Letzteres wohl in der Tat. Hätte ich exakt 3 Zeilen Lookahead in meinem Buch gemacht, dann wäre mein Suchcursor auf den Abschnitt mit der Überschrift "Lookaround doesn't consume text" gestoßen, der sicher auch ohne Mutmaßungen zur Antwort geführt hätte. :oops:
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Cool, so langsam werd ich warm mit den Dingern. Faszinierend, mit wie wenig Aufwand man sich schon nen simplen Tokenizer basteln kann (zumindest eine erste Idee davon). Gefällt mir und beginnt, wirklich Spass zu machen. :)

Code: Alles auswählen

In [1]: import re

In [2]: s = '[foo] bar [baz]'

In [3]: pattern = r"""(?P<open_bracket>\[) |
   ...:               (?P<close_bracket>\]) |
   ...:               (?P<text>.+?) (?=\[|\]|$)"""

In [4]: for m in re.finditer(pattern, s, re.VERBOSE):
   ...:     print '{0}({1!r})'.format(m.lastgroup, m.group())
   ...:     
   ...:     
open_bracket('[')
text('foo')
close_bracket(']')
text(' bar ')
open_bracket('[')
text('baz')
close_bracket(']')
Antworten