regulärer Ausdruck - suchen erst nach einem bestimmten Wort

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
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

Hallo,
in einem langen Text möchte ich mit regulären Ausdrücken nach folgendem suchen:
Residual: 0.1234
Ich weiß aber, dass 'Residual' genau zweimal vorkommt, möchte aber nur das zweite, das irgendwann nach 'Parameter' steht und auch erst ungefähr nach der 1000. Position.

Meine erste Idee war die Position, nach der er erst suchen soll, festzulegen, aber das gefällt mir nicht.

Code: Alles auswählen

with open( ausgabetext ) as f2:
	text = f2.read()
re11 = re.compile('Residual: [+|-]?\d[.]\d*')
re11.search(text,1000)
Meine zweite Idee funktionert aber auch nicht:

Code: Alles auswählen

re11.search(text,Parameter)
Gibt's da noch eine andere Möglichkeit?
Über ein paar Tipps wäre ich sehr dankbar.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Einmal suchen. Wenn gefunden, steht in match.end() die Position wo's aufhört. Von dort weitersuchen

Code: Alles auswählen

def find_second(p, s):
    m = re.search(p, s)
    if m:
        e = m.end()
        m = re.search(p, s[e:])
        if m:
            return m.start() + e
    return -1
Stefan
BlackJack

@sma: Man kann bei `re.search()` auch einen Startindex als Zahl angeben, dann braucht man die Teilzeichenkette nicht kopieren.

Edit: Zumindest bei der `search()`-Methode auf kompilierten Mustern gibt es diese Möglichkeit.

@kleiner.epsilon: Mit `finditer()` bekommt man einen Iterator über alle Treffer, da kann man einfach das erste Ergebnis wegwerfen und das zweite nehmen:

Code: Alles auswählen

residual_re = re.compile('Residual: ([+-]?\d[.]\d*)')
matches = residual_re.finditer(text)
matches.next()  # Skip first.
residual = matches.next().group(1)
In Deiner `re` war übrigens ein '|' zuviel. Das hätte auch 'Residual: |42.23' als Treffer angesehen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

BlackJack hat geschrieben:@sma: Man kann bei `re.search()` auch einen Startindex als Zahl angeben, dann braucht man die Teilzeichenkette nicht kopieren.
Mir war so, ich hatte extra geschaut, aber die Doku sagte:

Code: Alles auswählen

>>> help(re.search)
Help on function search in module re:

search(pattern, string, flags=0)
    Scan through string looking for a match to the pattern, returning
    a match object, or None if no match was found.
Also glaubte ich ihr. Warum kann denn die Modul-Funktion weniger als die Methode? Grummel.

Stefan
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

mmmh: also wenn ich folgende habe:

Code: Alles auswählen

residual_re = re.compile('Residual: [-]?\d[.]\d*')
matches = residual_re.finditer(text)
matches.next()  # Skip first.
residual = matches.next().group(1)
kommt bei mir folgende Fehlermeldung:

Traceback (most recent call last):
File "rahmenprogramm-neu.py", line 111, in <module>
residual = matches.next().group(1)
StopIteration

Habe versucht etwas zu ändern und verschiedenes ausprobiert, bin nun etwas ratlos.
deets

Das kommt dann, wenn dein Wort *garnicht* gefunden wird. Dann hat der Iterator nix zu iterieren, und dann kommt die Exception.

Vielleicht waere sowas hier fuer dich besser (ungetestet):

Code: Alles auswählen

for i, result in enumerate(residual_re.finditer(text)):
     if i == 1: # skip the first iteration
        break
else:
     raise Exception("residual_re hat nix gefunden")
print result
Und **ACHTUNG**: du hast ja einen gewissen Ruf, Exceptions einfach wegzufangen - das solltest hier natuerlich NICHT machen... denn ich denke mal, es ist ein wirklicher Fehler, wenn kein Ergebnis da ist.
BlackJack

@deets: Doch es wurde einmal gefunden -- sonst hätte das erste `next()` schon die `StopIteration` gebracht.
deets

@BlackJack

Stimmt, ja - aber meine Loesung sollte trotzdem robust sein, weil der else-zweig sowohl bei keiner als auch bei nur einer Loesung durchlaufen wird.
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

Die Ausdrücke kommen aber ganz sicher zweimal vor!

Folgendes hat jetzt funktioniert:

Code: Alles auswählen

re12 = re.finditer('Residual: [-]?\d*[.]\d*',text)
re12.next()
a12 = re12.next().group()
b12 = a12.split()
print b12[1]
Wie kann ich das etwas 'schöner' schreiben?
BlackJack

@kleiner.epsilon: Kommen die *ganz* sicher zweimal vor? Du hast nämlich jetzt den regulären Ausdruck etwas verändert -- der ursprüngliche hat nur Zahlen gefunden die genau eine Ziffer vor dem Dezimalpunkt stehen haben. Nun ist da noch ein Sternchen hinzugekommen.

Diese Nummerierung von Namen ist fast immer ein Zeichen von einem Entwurfsproblem. Meistens ist das ein Hinweis darauf, dass man eine Liste verwenden sollte. Hier ist das die Folge von einem immer wieder kehrenden Muster wo Du immer neue Namen brauchst. Das ist ein Hinweis darauf, dass man eine Funktion schreiben sollte, in der dieses Muster dann nur einmal steht.

Andererseits sind die Namen so nichtssagend. Wenn Du Zwischenergebnisse an Namen wie `a12` bindest, die überhaupt nichts zum Verständnis des Quelltextes beitragen, dann solltest Du das Zwischenergebnis am besten überhaupt gar nicht erst an einen Namen binden. Und die Objekte, die Du brauchst, sollten aussagekräftigere Namen als `re12` haben. Bezogen auf Deinen Quelltext:

Code: Alles auswählen

residual_matches = re.finditer('Residual: [-]?\d*[.]\d*', text)
residual_matches.next()
residual = residual_matches.next().group().split()[1]
print residual
Da das Muster mit dem Suchen ab einem bestimmten Versatz in den `text` öfter vorkommt, sollte man das aber vielleicht in eine Funktion kapseln. Auf Grundlage von deets' Ansatz (ungetestet):

Code: Alles auswählen

def re_find_nth(pattern, text, index=1, flags=0):
    """Find the n-th match.  Counting starts with zero.
    Default index is 1 == the second match.
    Returns `None` if there are not enough matches.
    """
    for i, match in enumerate(re.finditer(pattern, text, flags)):
        if i == index:
            return match
    return None

# …

residual = re_find_nth(r'Residual: (-?\d*\.\d*)', text).group(1)
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

@BlackJack: Echt cool, danke!
Ja bei der Vergabe der Namen habe ich geschlampt, weil das Programm noch in der Testphase ist, aber ich sollte von Anfang die Namen ordentlich benennen. :oops:

Ich habe noch eine kleine Frage zu der Funktion:

Was bedeutet das 'r' vor dem String? Warum muss das dort sein? Warum die Klamer () ?

Code: Alles auswählen

residual = re_find_nth(r'Residual: (-?\d*\.\d*)', text).group().split()[1]
Übrigens hat sich durch deine Hilfe bei der Definition der Funktion gleich schon ein zweites Problem von mir gelöst.
In meinem Text suche ich nach residual, p1, p2, p3, p4 und p5, die kommen genau zweimal vor und ich möchte nur den zweiten.
Außerdem suche nach divergence, convergence, matrix und squares, die kommen nur einmal vor.
Das kann ich nun im Funktionsaufruf einstellen.
Danke.

Code: Alles auswählen

	def re_find_nth(pattern, text, index=1, flags=0):
	    """Find the n-th match.  Counting starts with zero.
	    Default index is 1 == the second match.
	    Returns `None` if there are not enough matches.
	    """
	    for i, match in enumerate(re.finditer(pattern, text, flags)):
	        if i == index:
	            return match
	    return None

	residual = re_find_nth(r'Residual: (-?\d*\.\d*)', text).group().split()[1]
	parameter1 = re_find_nth('p1: -?\d*\.?\d*', text).group().split()[1]
	parameter2 = re_find_nth('p2: -?\d*\.?\d*', text).group().split()[1]
	parameter3 = re_find_nth('p3: -?\d*\.?\d*', text).group().split()[1]
	parameter4 = re_find_nth('p4: -?\d*\.?\d*', text).group().split()[1]
	parameter5 = re_find_nth('p5: -?\d*\.?\d*', text).group().split()[1]

	squares = re_find_nth('SQUARES \s*-?\d*\.\d*[E]?[+-]?\d?\d?\d?', text, index=0).group().split()[1]
	convergence = re_find_nth('No corrector convergence', text, index=0)
	divergence = re_find_nth('divergence', text, index=0)
	matrix = re_find_nth('Matrix', text, index=0)
Übrigens hat sich durch deine Hilfe bei der Definition der Funktion gleich schon ein zweites Problem von mir gelöst.
In meinem Text suche ich nach residual, p1, p2, p3, p4 und p5, die kommen genau zweimal vor und ich möchte nur den zweiten.
Außerdem suche nach divergence, convergence, matrix und squares, die kommen nur einmal vor.
Das kann ich nun im Funktionsaufruf einstellen. :D
Danke.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

kleiner.epsilon hat geschrieben:Was bedeutet das 'r' vor dem String? Warum muss das dort sein? Warum die Klamer () ?
Das r steht für Raw-Strings und bedeutet dass Escape-Sequenzen wie ``\n`` nicht ausgewertet werden. Das heißt aber auch, dass man Backslashes, die doch relativ häufig in Regular Expressions vorkommen nicht escapen muss, und das macht sie etwas übersichtlicher. Die Klammer hingegen bedeutet, dass der ganze Match in einer Gruppe gefangen wird, worauf du etwa mit ``matchobjekt.groups(n)`` zugreifen kannst.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

@kleiner.epsilon: 'r' vor einem Zeichenkettenliteral bedeutet, dass die Backslashes in dem Literal für den Compiler keine besondere Bedeutung mehr haben. '\n' ist ein Zeichen -- nämlich das Zeilenende-Zeichen; r'\n' sind zwei Zeichen, nämlich ein Backslash und ein 'n'. Bei dem Beispiel macht es keinen Unterschied, aber da Backslashes in regulären Ausdrücken auch eine besondere Bedeutung haben, wird es manchmal verwirrend, wenn man Anfangen muss die Sachen doppelt zu escapen -- einmal für den Python-Compiler und dann für den Compiler die den regulären Ausdruck verarbeitet.

Die Klammer ist eine Gruppe im regulären Ausdruck. Schau Dir doch noch mal meinen Code weiter oben an. Da benutze ich klein ``.group().split()[1]`` sondern nur ``.group(1)`` um die erste Gruppe aus dem Treffer zu bekommen. Die Zählung beginnt da bei 1. Die Gruppe 0 ist immer der gesamte Treffer.

Die Einrückung der Funktionsdefinition lässt vermuten das das innerhalb der Schleife steht. Das bedeutet, dass diese Funktion bei jedem Schleifendurchlauf aufs Neue definiert wird. Das ist unnötige Arbeit. Nicht viel, aber IMHO unschön.
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

Aha, hab verstanden. Danke.

Nun ist ein weiteres Problem entstanden: Was bedeutet diese Fehlermeldung?:
Traceback (most recent call last):
File "rahmenprogramm-neu.py", line 91, in <module>
residual = re_find_nth(r'Residual: (-?\d*\.\d*)', text).group(1)
AttributeError: 'NoneType' object has no attribute 'group'

Meine Vermutung ist, dass es etwas mit der Funktiondefinition zu tun hat.
Ich möchte nämlich eigentlich, dass wenn er gar keinen Treffer gefunden hat, dass er dann nichts macht,
und das Programm dann einfach im else-Zweig, der noch kommt, weiter läuft. Ist das so hier?

Code: Alles auswählen

def re_find_nth(pattern, text, index=1, flags=0):
    """Find the n-th match.  Counting starts with zero.
    Default index is 1 == the second match.
    Returns `None` if there are not enough matches.
    """
    for i, match in enumerate(re.finditer(pattern, text, flags)):
        if i == index:
            return match
    return None
BlackJack

@kleiner.epsilon: Die Funktion gibt `None` zurück wenn kein Treffer im Text ist, beziehungsweise wenn es nicht genug Treffer gibt um den n-ten zu liefern. Das steht doch im Docstring der Funktion.

Und wenn sie `None` zurück gibt, dann kann man darauf nicht `group()` Aufrufen. Das sagt Dir die Fehlermeldung. Also müsstest Du die Rückgabe auf `None` prüfen. Genau wie man das bei den normalen `re`-Methoden auch machen müsste.
kleiner.epsilon
User
Beiträge: 25
Registriert: Sonntag 31. Oktober 2010, 14:31

Tut mir leid, ich verstehe das nicht.
Also das man auf 'None' nicht 'group()' aufrufen kann, ist klar.
Aber deine letzten beide Sätze habe ich nicht verstanden, sorry.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

kleiner.epsilon hat geschrieben:Tut mir leid, ich verstehe das nicht.
Also das man auf 'None' nicht 'group()' aufrufen kann, ist klar.
Wenn das klar ist, was ist dann an den folgenden Sätzen nicht klar?

Code: Alles auswählen

# statt
residual = re_find_nth(r'Residual: (-?\d*\.\d*)', text).group().split()[1]
# eben so
result = re_find_nth(r'Residual: (-?\d*\.\d*)', text)
if result is not None:
    residual = result.group().split()[1]
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten