|
UlfZibis
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Hallo an die Python-Spezialisten hier, bei der Weiterentwicklung meines Skripts bin ich auf ein Problem gestoßen. Folgendes Regex-Muster sollte nach Regular Expressions Sheet/"Groups and Ranges" zum Entfernen eines Leerzeichen aus dem Ergebnis führen: (?:\s) Zumindest unter Python tut es das aber nicht. folgende Programmzeile soll eine Fließkommazahl aus einer Textzeile extrahieren, und dabei das ggf. hinter der Tausenderziffer vorhandene Leerzeichen ignorieren: | text = "Blih blah 2 345.67 kb/s blupp."
extract = re.search(r"[-\d.]*(?:\s)[\d.]*", text)[0]
bit_rate = float(extract) ### scheitert mit Programmabbruch
|
Gewünschtes Ergebnis: extract=2345.67 Tatsächliches Ergebnis: extract=2 345.67 Woran könnte das liegen? Hat Python-RegEx für diesen Zweck eine andere Syntax?
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Und vielleicht kennt auch jemand diesen Unterschied: unterschied-python3-pymediainfo-vs-python3-med
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis: Das entfernt nichts. Und nicht nur in Python nicht. Das Leerzeichen wird gematcht, landet aber in keiner Gruppe. Es macht auch nicht wirklich Sinn das in eine „non-capturing group“ zu packen. Das hier macht das gleiche: r"[-\d.]*\s[\d.]*". Und ich kann das Ergebnis auch nicht nachvollziehen. Da das vor und nach dem Leerzeichen auch 0 Zeichen lang sein darf (*) matcht das im Beispiel das erste Leerzeichen in text also das zwischen "Blih" und "blah". Das muss noch an irgendwas anderem verankert werden, sonst funktioniert das so nicht.
| text = "Blih blah 2 345.67 kb/s blupp."
if match := re.search(r"([-\d.]*)\s([\d.]*)\skb/s", text):
bit_rate = float(match[1] + match[2])
print(bit_rate)
|
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: @UlfZibis: Das entfernt nichts. Und nicht nur in Python nicht. Das Leerzeichen wird gematcht, landet aber in keiner Gruppe. Es macht auch nicht wirklich Sinn das in eine „non-capturing group“ zu packen. Das hier macht das gleiche: r"[-\d.]*\s[\d.]*".
Dann hab' das ich wohl falsch interpretiert als "nicht aufzuzeichnende Gruppe". Aber was kann man denn dann mit dem Muster "(?:...)" sonst sinnvolles machen?
Und ich kann das Ergebnis auch nicht nachvollziehen. Da das vor und nach dem Leerzeichen auch 0 Zeichen lang sein darf (*) matcht das im Beispiel das erste Leerzeichen in text also das zwischen "Blih" und "blah". Das muss noch an irgendwas anderem verankert werden, sonst funktioniert das so nicht.
Stimmt. Ich hatte mich vertan. Meine Daten sehen eigentlich so aus (das "blih blah blupp" wird ja schon vorher rausgefiltert und dann braucht's den weiteren Anker nicht): Schade auch, dass die float-Konversion nicht einfach alles andere hinter den Zahlen ignoriert.
| text = "Blih blah 2 345.67 kb/s blupp."
if match := re.search(r"([-\d.]*)\s([\d.]*)\skb/s", text):
bit_rate = float(match[1] + match[2])
print(bit_rate)
|
Genau auf diesen "Workaround" (ohne "\skb/s") bin ich auch gekommen, doch ich hoffte, es gibt einen kompakten Einzeiler. Aber vielleicht geht ja was mit assertions, z.B.: | bit_rate = float(matches[0]) if (matches := re.search(r"[-\d.]*(?=\s)[\d.]*", text)) else 0.0
|
Klappt aber leider nur für "345.67 kb/s" und | bit_rate = float(matches[0]) if (matches := re.search(r"[-\d.]*(?<=\s)[\d.]*", text)) else 0.0
|
nur für "2 345.67 kb/s". Vielleicht hast Du ja noch eine Idee. Vielleicht mit Conditionals "?()", kapier' aber die Syntax nicht?
|
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 266
|
Moin, UlfZibis schrieb: Dann hab' das ich wohl falsch interpretiert als "nicht aufzuzeichnende Gruppe". Aber was kann man denn dann mit dem Muster "(?:...)" sonst sinnvolles machen?
Die brauchst du z.B. wenn eine Zeichenkonstellation mal vorhanden sein kann und mal nicht. Je nachdem, ob diese Gruppe vorhanden ist oder nicht, würden sonst die Gruppen hinter der Konstellation anders nummeriert werden. Mit solchen passiven Gruppen verhinderst du das eben, da die passive Gruppe nicht im Match nummeriert wird.
Genau auf diesen "Workaround" (ohne "\skb/s") bin ich auch gekommen, doch ich hoffte, es gibt einen kompakten Einzeiler.
Warum nicht...
| re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', text)
|
oder eben
| re.sub(' ', '', re.search(r'[-\d]+\s[.\d]+',text)[0])
|
Gruß Thomas
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
TK87 schrieb: Die brauchst du z.B. wenn eine Zeichenkonstellation mal vorhanden sein kann und mal nicht. Je nachdem, ob diese Gruppe vorhanden ist oder nicht, würden sonst die Gruppen hinter der Konstellation anders nummeriert werden. Mit solchen passiven Gruppen verhinderst du das eben, da die passive Gruppe nicht im Match nummeriert wird.
Interessant. Eine normale Gruppe (...), die kein Ergebnis liefert, bringt also die Nummerierung durcheinander. Gleich 2 neue Dinge gelernt. 👍 Warum nicht...
| re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', text)
|
oder eben
Weil ich nicht drauf gekommen bin. Herzlichen Dank für die beiden eleganten Vorschläge.
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis: Gruppen ohne Ergebnis ändern nichts an der Nummerierung, das wäre ziemlich nervig. Manchmal braucht man eine Gruppe nur zum Klammern eines Teilmusters, und da muss man dann halt keine Nummer/Aufzeichnung für ”ver(sch)wenden”. Zum Beispiel für ein Muster das name = name, name, name, … matcht und wo man an den beiden Teilen links und rechts vom = interessiert ist: (\w+)\s*=\s*(\w+(?:\s*,\s*\w+)*). Da hätte man sonst drei Gruppen, die eine interessiert aber gar nicht wirklich, die ist nur dazu da, damit das * auf den Teilausdruck angewendet werden kann. Oder das hier: ((?:(?:con|de)struc|interrup)tor)\s+([a-z]\w*) um verschiedene Alternativen in der ersten Gruppe zu haben (constructor, destructor, und interruptor) wo einen die Teilworte im Ergebnis nicht interessieren, sondern nur das gesamte Wort eine Gruppe im Ergebnis haben soll. Insbesondere wenn man solche Optimierungen nachträglich einbaut, will man nicht das sich die Gruppennummern ändern, weil man dann ja auch den Code ändern muss, der das Ergebnis dann nutzt. Ich finde das überhaupt nicht schade das float() & Co nicht einfach Zeichen ignorieren. Das ist total nervig weil es extra Aufwand bedeutet das dann zu erkennen, dass da Zeug ist was da vielleicht gar nicht hingehört und einen Fehler auslösen sollte. Das Problem hatte ich gerade erst in einer anderen Programmiersprache die diesen Unsinn so macht. Praktisch wenn man quick'n'dirty was umwandeln will von dem man ausgeht, dass es okay ist, aber extra nervig wenn man Fehler in den Eingaben dann immer mit extra Code erkennen und behandeln muss. Extra blöd wenn Eingaben die gar keine gültige Zahl enthalten dann einfach als 0 gewertet werden. @TK87: Um ein Leerzeichen oder allgemein einen festen Text zu ersetzen ist ein regulärer Ausdruck übertrieben. Das wäre einfach text.replace(" ", "").
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: @UlfZibis: Gruppen ohne Ergebnis ändern nichts an der Nummerierung, das wäre ziemlich nervig.
Danke für die Erleichterung. ☺ Manchmal braucht man eine Gruppe nur zum Klammern eines Teilmusters, und da muss man dann halt keine Nummer/Aufzeichnung für ”ver(sch)wenden”. .....
Ah, jetzt endlich ganz verstanden. Solche Gruppen werden dann dennoch (hier in matches[0]) aufgezeichnet, sind aber eben nicht über Nummern (matches[n]) separat abrufbar. Dann ist "non-capturing group" also eine mindestens irreführende wenn nicht falsche Kurzerklärung. Besser wäre also sowas wie "non-numbered ...", "not counted ..." oder non-enumerated ...". Neben Deinen sehr guten Beispielen macht das auch Sinn, wenn mehr als 9 Klammerungen im Muster sind, weil man sonst auf weiter hinten liegende Gruppen keinen separaten Zugriff mehr hätte.
Ich finde das überhaupt nicht schade das float() & Co nicht einfach Zeichen ignorieren. Das ist total nervig weil es extra Aufwand bedeutet das dann zu erkennen, dass da Zeug ist was da vielleicht gar nicht hingehört und einen Fehler auslösen sollte. Das Problem hatte ich gerade erst in einer anderen Programmiersprache die diesen Unsinn so macht. Praktisch wenn man quick'n'dirty was umwandeln will von dem man ausgeht, dass es okay ist, aber extra nervig wenn man Fehler in den Eingaben dann immer mit extra Code erkennen und behandeln muss. Extra blöd wenn Eingaben die gar keine gültige Zahl enthalten dann einfach als 0 gewertet werden.
Guter Hinweis. Wenn, sollte das also durch ein Extra-Property/Flag steuerbar sein.
|
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2136
|
Eine non-capturing group ist eine nicht erfasste Gruppe in dem Sinne, dass man nicht im Nachgang auf sie zugreifen kann, wie ja bereits erklärt wurde. Damit beschreibt der Begriff doch genau den Effekt.
|
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2136
|
UlfZibis schrieb: Danke für die Erleichterung. ☺ Manchmal braucht man eine Gruppe nur zum Klammern eines Teilmusters, und da muss man dann halt keine Nummer/Aufzeichnung für ”ver(sch)wenden”. .....
Ah, jetzt endlich ganz verstanden. Solche Gruppen werden dann dennoch (hier in matches[0]) aufgezeichnet, sind aber eben nicht über Nummern (matches[n]) separat abrufbar.
Die Gruppe 0 ist immer der gesamte Treffer, das ist somit hier als Beispiel nicht so aussagekräftig. Die "anonyme" Gruppe wird generell als Hilfsmittel für ein Pattern benutzt, z. B. um zu sagen, dass möglicherweise noch eine Gruppe von Zeichen kommt, bevor das gewünschte Teilstück erreicht ist. An diesen Zeichen bin ich aber später nicht interessiert, also brauche ich keinen Zugriff darauf.
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
snafu1 schrieb: ... in dem Sinne, ...
Ja genau, aber in welchem Sinne genau gibt die Kurzbeschreibung nicht her. Deshalb wäre sowas wie "non-numbered ...", "not counted ..." oder non-enumerated ..." IMHO präziser. Generell, also "nicht-nummeriert" in Gruppe 0 oder ganz "normal" per grep, wird sie ja eben doch "erfasst".
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
TK87 schrieb: | re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', text)
|
Leider funktioniert das nicht für Werte < 1000 : | if matches := re.search(r"([-\d.]*)\s([\d.]*)", "2 345.67 kb/s") :
bit_rate = float(matches[1]+matches[2])
print(bit_rate)
if matches := re.search(r"([-\d.]*)\s([\d.]*)", "345.67 kb/s") :
bit_rate = float(matches[1]+matches[2])
print(bit_rate)
bit_rate = float(match) if (match := re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', "2 345.67 kb/s")) else 0.0
print(bit_rate)
bit_rate = float(match) if (match := re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', "345.67 kb/s")) else 0.0
print(bit_rate)
|
Ergebnis: 2345.67
345.67
2345.67
Traceback (most recent call last):
File "/mnt/Daten/Users/ich/Momentum/./volume-analysis.py", line 247, in <module>
main()
File "/mnt/Daten/Users/ich/Momentum/./volume-analysis.py", line 154, in main
bit_rate = float(match) if (match := re.sub(r'\D*([-\d]+)\s([.\d]+).*', r'\1\2', "345.67 kb/s")) else 0.0
^^^^^^^^^^^^
ValueError: could not convert string to float: '345.67 kb/s'
Leider funktioniert die "re.sub(..., r'\1\2', ...)"-Technik aber auch mit meinem Pattern nicht, obwohl es mit "matches[1]+matches[2]" funktioniert: | if matches := re.search(r"([-\d.]*)\s([\d.]*)", "2 345.67 kb/s") :
bit_rate = float(matches[1]+matches[2])
print(bit_rate)
if matches := re.search(r"([-\d.]*)\s([\d.]*)", "345.67 kb/s") :
bit_rate = float(matches[1]+matches[2])
print(bit_rate)
bit_rate = float(match) if (match := re.sub(r"([-\d.]*)\s([\d.]*)", r'\1\2', "2 345.67 kb/s")) else 0.0
print(bit_rate)
bit_rate = float(match) if (match := re.sub(r"([-\d.]*)\s([\d.]*)", r'\1\2', "345.67 kb/s")) else 0.0
print(bit_rate)
|
Ergebnis: 2345.67
345.67
Traceback (most recent call last):
File "/mnt/Daten/Users/ich/Momentum/./volume-analysis.py", line 247, in <module>
main()
File "/mnt/Daten/Users/ich/Momentum/./volume-analysis.py", line 152, in main
bit_rate = float(match) if (match := re.sub(r"([-\d.]*)\s([\d.]*)", r'\1\2', "2 345.67 kb/s")) else 0.0
^^^^^^^^^^^^
ValueError: could not convert string to float: '2345.67kb/s'
Was läuft denn da jetzt noch schief?
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Wie wäre es damit?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | import re
BITRATE_RE = re.compile(r'(?:(?P<thousands>\d+)\s)*(?P<hundreds>\d+)(?P<decimal_places>\.\d+)?')
examples = [
"2 345.67 kb/s",
"345.67 kb/s",
]
for e in examples:
try:
bitrate = float("".join(BITRATE_RE.match(e).groupdict("").values()))
except ValueError:
bitrate = 0.0
print(bitrate)
|
PS: wie kommt man zu einem negativen Wert für die Bitrate in dem ursprünglichen Regex?
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
Na das hatten wir doch schon mal: Da sowohl die erste als auch die zweite Gruppe auch leer sein darf, ersetzt das jedes Leerzeichen, denn es matcht auf jedes Leerzeichen. Und das "kb/s" wird nicht beseitigt. Ich war übrigens erst mal verwirrt was Du da match nennst, weil das gar kein re.Match-Objekt ist, sondern eine Zeichenkette. Und matches ist auch eher ein Name für eine Sequenz oder einen Iterator über re.Match-Objekte und nicht für ein solches Objekt. Kann es überhaupt negative Bitraten geben? Und Punkte zwischen den Ziffern vor dem Leerzeichen? Ich würde den Ausdruck ein bisschen näher an dem formulieren was man da erwartet Also Optional Ziffern gefolgt von einem Leerzeichen, dann Ziffern, ein Punkt, Ziffern, Leerzeichen und "kb/s". Und dann einfach vor dem Umwandeln in eine Zahl das Leerzeichen entfernen. Ungetestet: | match = re.search(r"((?:\d+ )?\d+\.\d+) kb/s", text)
bit_rate = float(match[1].replace(" ", "")) if match else 0.0
|
Allerdings würde ich hier auch noch mal schauen was die Quelle von diesem Text bei Mega statt Kilo ausgibt! Eventuell kann es dann mehr als das eine Leerzeichen geben, oder die Einheit ist dann nicht mehr "kb/s".
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@seahawk1986: Reichlich umständlich mit den benannten Gruppen IMHO und die <thousands> könnte theoretisch mehr als einmal matchen wenn die Zahl wirklich gross wird, also "12 345 678.90 kb/s" beispielsweise. Dann geht die 12 verloren, weil sich die Gruppe nur das letzte match merkt. Die ursprüngliche Regex erkennt auch "-1 234.56 kb/s" und das wäre dann negativ. Die ursprüngliche Regex erkennt auch "12-3.4-4.2 9.45.45.6 kb/s" — was dann am float() scheitern wird. ☺
|