RegExp Kurs: Python

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
TRex
Moderator
Beiträge: 8038
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

RegExp Kurs: Python

Beitrag von TRex » 12.06.2022 09:06:33

Hi,

in diesem Thread geht es um die Anwendung von regulären Ausdrücken in Python.

Am Ende dieser Lerneinheit solltet ihr verstanden haben, welche Eigenheiten Python im Kontext mit sich bringt und mit regulären Ausdrücken darin sinnvoll arbeiten können. Hilfreich sind grundlegende Python-Kenntnisse und natürlich die vorhergehenden Einheiten von Meillo.

Kurzeinstieg in Python

Für Neueinsteiger: Python ist eine sogenannte Interpretersprache, man kann also Code ausführen, ohne dass eine Kompilierung notwendig wäre (vergleichbar mit der Shell).

Im Folgenden werde ich längere Code-Snippets in Form eines kompletten ausführbaren Scripts halten, und einzelne Zeilen mit dem Interpreter-Prompt von Python markieren, ">>>" (analog zum Dollarzeichen der bash).

Getestet habe ich die Code-Teile übrigens mit Python 3.7, aber jede Python-Version ab 3.x aus den letzten Jahren sollte verwendbar sein.

Grundlegende Verwendung

Kurzer kommentierter Stoß ins kalte Wasser, um ein vollständiges Beispiel zu haben:

Code: Alles auswählen

# Die RegEx-Funktionalitäten stecken in diesem Modul der Python standard library
import re


text = "Ich weiß gar nicht, was ich alles nicht weiß, aber es ist sicher viel"

# re.findall sucht nach allen Vorkommen des Ausdrucks, ähnlich wie grep, und gibt sie als Liste zurück
kurze_woerter = re.findall(r"[A-Za-z]{1,4}", text)

print(kurze_woerter)
Das Beispiel tut übrigens nicht, was es andeutet, aber es ist syntaktisch korrekt. Überfliegen wir kurz, was hier passiert:

1. Zu Beginn importieren wir das Modul re aus der Standardbibliothek von Python. Die Doku von Python ist recht gut und alles, was ich hier über dieses Modul erwähne, wird auch hier erklärt: https://docs.python.org/3/library/re.html
2. Eine Variable mit etwas Beispieltext wird zugewiesen.
3. Die Funktion findall aus dem Modul re wird mit einem regulären Ausdruck und dem Beispieltext aufgerufen und gibt eine Liste mit allen Funden zurück, welche der Variable kurze_woerter zugewiesen wird.
4. Diese Liste wird (auf der Konsole) ausgegeben.

"raw-Strings"

Das erste auffällige Element in dem Script ist die Verwendung des Zeichen r vor dem in Anführungszeichen umschlossenen Ausdrucks. In Episode 2 des Kurs haben wir gelernt, dass wir abhängig von der Umgebung Zeichen escapen müssen. Hier trifft das ebenso zu, wenn auch nicht in diesem Beispiel. Möchte man aber in Python backslashes verwenden, muss man diese entweder mit einem weiteren backslash escapen oder den String mit dem r zu einem raw-String machen.

Code: Alles auswählen

>>> print("Hallo\nWelt")
Hallo
Welt
>>> print(r"Hallo\nWelt")
Hallo\nWelt
Will ich also auf einen Zeilenumbruch matchen, bleibt mir die Wahl zwischen "\\n" und r"\n". Es gibt Gründe, ersteres zu verwenden, aber ich würde sie der Komplexität halber vermeiden, soweit möglich (nicht nur des Kurs wegen, sondern auch, weil man die Ergebnisse anschließend schwerer nachvollziehen kann).

Kompilierte Ausdrücke

Im Beispiel verwende ich re.findall aus dem Modul heraus und übergebe einen raw-String. Das ist kompakt und in diesem Beispiel ohne Nachteile. Möchte ich aber den gleichen Ausdruck mehrfach verwenden (in einer Schleife oder an einer zweiten Stelle im Code), bietet es sich an, den Ausdruck zu "kompilieren":

Code: Alles auswählen

>>> hex_digits = re.compile(r"[0-9a-f]")
>>> hex_digits.findall("ff00ad")
['f', 'f', '0', '0', 'a', 'd']
Das gibt dem Ausdruck nicht nur einen Namen (ich hätte den String ja auch einfach so abspeichern können), sondern erspart auch die wiederholte Kompilierung (die ansonsten im Hintergrund bei jeder Verwendung des raw-Strings geschieht).
Anstelle des Moduls verwendet man dann den kompilierten Ausdruck beim Aufruf, und der erste Parameter zu findall entfällt (logischerweise).

Dialekte: Python re, ERE, PCRE

Der Dialekt von Python orientiert sich an der Perl-Implementierung (PCRE), ist jedoch eine eigenständige Implementierung.

Es gibt also Konstrukte wie \w und eher abenteuerliche Gruppenmodifikatoren (Gruppen kommen noch), man kann sich aber nicht völlig auf bisheriges Wissen verlassen und sollte im Zweifel die Doku konsultieren und einen Test für seine Anwendung schreiben. Da ich mit verschiedenen Tools arbeite und mir nie merken kann, welches Tool genau welche Features unterstützt, mache ich das auch nicht anders.

Eine gute Zusammenfassung habe ich hier gefunden: https://stackoverflow.com/questions/339 ... bre-or-ere

Ein Vergleich mit anderen Dialekten hier: https://remram44.github.io/regex-cheatsheet/regex.html

Funktionen im Modul

Bisher habe ich mich auf findall beschränkt, es gibt aber noch einige weitere Funktionen. Die meisten davon arbeiten mit Match-Objekten, welche neben dem gematchten Text auch noch einige weitere Informationen enthalten. Den ersten vollständigen Treffer erhält man mit .group().

1. search: sucht an einer beliebigen Stelle im Suchtext nach einem Treffer und gibt diesen (ersten) als Match-Objekt zurück oder None, wenn nichts gefunden wurde.

Code: Alles auswählen

>>> re.search("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel").group()
'Erdbirne'
>>>
2. match: wie search, beginnt aber am Anfang des Suchtextes.

Code: Alles auswählen

>>> re.match("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel")
# Keine Ausgabe, Rückgabewert None - kein Treffer!
>>> re.match("Erd(apfel|birne)|Kartoffel", "Erdbirne Erdapfel").group()
'Erdbirne'
# Ohne group sehen wir nur das match-Objekt:
>>> re.match("Erd(apfel|birne)|Kartoffel", "Erdbirne Erdapfel")
<_sre.SRE_Match object at 0x7f6171b7e648>
>>>
3. fullmatch: noch eine Stufe weiter, der gesamte Suchtext muss auf den Ausdruck passen.

Code: Alles auswählen

>>> re.match("Erd(apfel|birne)|Kartoffel", "Erdbirnen").group()
Erdbirne
>>> re.fullmatch("Erd(apfel|birne)|Kartoffel", "Erdbirnen")
# nichts
4. findall: prinzipiell weiter oben bereits erklärt, eine Besonderheit gibt es aber (nebst der Tatsache, dass es keine Match-Objekte verwendet). Befinden sich Gruppen (capturing groups, kleiner Vorgriff zur Vollständigkeit) im Ausdruck, werden diese bevorzugt (und exklusiv zurückgegeben). Heißt für unser Musterbeispiel:

Code: Alles auswählen

>>> re.findall("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel")
['birne', 'apfel']
# "Entschärft", noncapturing group
>>> re.findall("Erd(?:apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel")
['Erdbirne', 'Erdapfel']
>>>
5. finditer: analog zu findall, statt einer Liste wird ein Iterator zurückgegeben. Es muss also nicht das vollständige Resultat im Speicher gehalten werden. Außerdem ist es die einzige Funktion, die mehrere Treffer erlaubt und Match-Objekte zurückgibt.

Code: Alles auswählen

>>> food = re.finditer("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel")
>>> food
<callable-iterator object at 0x7f6171b85050>
# Ein Weg, um einen Iterator zu durchlaufen, ist next()
>>> next(food)
<_sre.SRE_Match object at 0x7f6171b7e6c0>
>>> next(food)
<_sre.SRE_Match object at 0x7f6171b7e738>
# Und das passiert, wenn alle Treffer durchlaufen wurden:
>>> next(food)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
6. sub: suchen und ersetzen, auch mit Gruppen.

Code: Alles auswählen

>>> re.sub("Erd(apfel|birne)|Kartoffel", "Schokolade", "Grumbeere Erdbirne Erdapfel")
'Grumbeere Schokolade Schokolade'
>>>
Gibt noch ein paar weitere, aber diese sind die relevanten Funktionen zum matchen von regulären Ausdrücken.

Jede dieser Funktionen unterstützt Modifikatoren, wie beispielsweise das Ignorieren von Groß- und Kleinschreibung:

Code: Alles auswählen

>>> re.findall("debianforum.de", "Debianforum.de", re.IGNORECASE)
['Debianforum.de']
>>>
Auch besonders interessant ist re.MULTILINE (kurz auch re.M): normalerweise kann man mit ^ und $ nur eine Zeile matchen.

Code: Alles auswählen

>>> re.findall("^debianforum", "Debianforum.de\ndas beste Forum auf der ganzen Welt\ndebianforum ist toll", re.I)
['Debianforum']
>>>
Das ^ match hier nur am Anfang der Zeichenkette, nicht am Zeilenbeginn in der Mitte. Für re.M greife ich noch in eine weitere Trickkiste: man kann die Flags binär verknüpfen.

Code: Alles auswählen

>>> re.findall("^debianforum", "Debianforum.de\ndas beste Forum auf der ganzen Welt\ndebianforum ist toll", re.I | re.M)
['Debianforum', 'debianforum']
>>>
Auf diese Weise wird jede Zeile im Ausdruck als solche behandelt, und durch das Kombinieren der Flags matcht auch das "Debianforum".

Es gibt noch ein paar weitere Flags, die ihr gerne im verlinkten Abschnitt der Dokumentation nachschlagen könnt - brauchen werdet ihr die aber eher selten. re.VERBOSE ist für komplizierte Ausdrücke möglicherweise hilfreich und re.DEBUG könnte vielleicht auch irgendwann einmal helfen, einen "historisch gewachsenen" Ausdruck zu zerlegen.


Im Übrigen wäre es hier natürlich sinnvoll gewesen, die Ausdrücke zu kompilieren... aber dann wären die Einzelabschnitte nicht mehr separat les-/nutzbar gewesen.

Gruppen/Unterausdrücke

In Kapitel 4 haben wir "Unterausdrücke" kennengelernt. Python nennt sie "subgroups", also Untergruppen - die Benennung ist aber meiner bescheidenen Meinung nach nicht ganz einheitlich, wenn man auf die Funktionen schaut.

Aus dem vorigen Kapitel stammt folgender Ausdruck:

Code: Alles auswählen

>>> re.search("Erd(apfel|birne)|Kartoffel", "Erdbirne").groups()
('birne',)
(apfel|birne) ist die erste (Unter-)Gruppe. Mit group kann man mehr als nur das gesamte Ergebnis abfragen:

Code: Alles auswählen

>>> re.search("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel").group()
'Erdbirne'
>>> re.search("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel").group(0)
'Erdbirne'
>>> re.search("Erd(apfel|birne)|Kartoffel", "Grumbeere Erdbirne Erdapfel").group(1)
'birne'
>>>
Wenn man nun jedoch seine Gruppen benennt, kommt man über diese Namen auch an bestimmte Gruppen:

Code: Alles auswählen

>>> match = re.match(r"(?P<vorname>\w+) (?P<nachname>\w+)", "Linus Torvalds")
>>> match.groups()
('Linus', 'Torvalds')
>>> match.group()
'Linus Torvalds'
>>> match.groupdict()
{'nachname': 'Torvalds', 'vorname': 'Linus'}
>>> match.groupdict()["nachname"]
'Torvalds'
Möchte ich mehrere Namen auf diese Weise strukturiert in einem Suchtext finden, könnte ich das mit finditer machen:

Code: Alles auswählen

>>> for match in re.finditer(r"(?P<vorname>\w+) (?P<nachname>\w+)", "Linus Torvalds, Sebastian Feltel, Meillo Meillo"):
...     print(match.groupdict())
...
{'nachname': 'Torvalds', 'vorname': 'Linus'}
{'nachname': 'Feltel', 'vorname': 'Sebastian'}
{'nachname': 'Meillo', 'vorname': 'Meillo'}
>>>
Was ich auch mit Gruppen machen kann: sie in anderen Ausdrücken referenzieren.

Code: Alles auswählen

>>> re.sub("Erd(apfel|birne)|Kartoffel", r"Glueh\1", "Grumbeere Erdbirne Erdapfel")
'Grumbeere Gluehbirne Gluehapfel'
>>>
Hier ist \1 die Referenz, wobei \1 den ersten, \2 den zweiten Unterausdruck usw. bezeichnet, der dann an der Stelle eingesetzt wird.

Man kann Gruppen auch beliebig verschachteln, die Lesbarkeit nimmt allerdings stark ab:

Code: Alles auswählen

>>> match = re.match(r"Meine (?P<what>(?P<howmany>[0-9]+) (\w+) Ausdruecke)", "Meine 5 stoerrischen Ausdruecke")
>>> match
<_sre.SRE_Match object at 0x7f6171c23880>
>>> match.groupdict()
{'what': '5 stoerrischen Ausdruecke', 'howmany': '5'}
>>> match.groups()
('5 stoerrischen Ausdruecke', '5', 'stoerrischen')
Unbenannte Gruppen bleiben dem groupdict() einfach fern - ist also eine Option. groups() serialisiert die Verschachtelung von links nach rechts.

Die Verschachtelung von Gruppen mit Quantifizierung... das geht, es matcht, aber der Zugriff auf die Untergruppen ist weder numerisch noch benannt nur sehr beschränkt möglich - lasst es lieber (für den Fall, dass ich damit jemandem ein grausames Schicksal ersparen kann).


Typische Anwendungen

Ich kann mit curl und grep sehr viele Informationen aus Webseiten extrahieren, aber dann? Die Datenverarbeitung in der bash ist ziemlich limitiert. Mit Python könnt ihr vielerlei Quellen anzapfen (Datenbanken, Webseiten, spezielle Dateiformate) und die extrahierten Informationen direkt weiterverwenden, und dazwischen allerlei Hilfsmittel einsetzen, um die Daten zu validieren und beispielsweise wieder in strukturierter Form abzulegen.

Für gewöhnlich verwende ich das 3rd-Party-Modul requests, Debianpython3-requests, für HTTP-Requests. Auch die offizielle Doku empfiehlt dies, die Installation lohnt sich auf jeden Fall. Für das folgende Beispiel ist das notwendig.

Nehmen wir an, ihr hättet eine Liste von Youtube-Links. Ihr hättet gerne die Titel. Aus Erfahrung kann ich sagen, dass ein einfaches curl und die Suche nach dem title-Element bei Youtube nicht gut funktionieren. Dank eggy kenne ich aber noch einen Weg - wenn auch ein wenig umständlich. Für Python jedoch kein Problem. Man könnte damit nun durch den Musik-Thread oder die sqlite-DB von gajim iterieren - eine Fingerübung für interessierte, die dann aber nicht mehr mit Regex zu tun hat ;)

Das Programm (herausgelöst aus anderem Code): NoPaste-Eintrag41710

Also aus einem beliebigen Text (hier: nur eine) Youtube-URL identifizieren (in verschiedenen Formaten), die ID extrahieren und über den kleinen Umweg den Titel aus einer HTTP-Antwort klauben.


Übung 2 bietet Gelegenheit, etwas ähnliches zusammenzubasteln.


Performance

Python ist nicht besonders schnell, wenn man es mit kompilierten Sprachen vergleicht. Das heißt aber nicht, dass man keinen Unterschied merkt zwischen der erstbesten Implementierung und einer optimierten.

Für reguläre Ausdrücke bedeutet das:

1. Ausdrücke kompilieren (bereits erwähnt)
2. Suchraum minimieren
3. Lieber zwei kleine Ausdrücke als ein großer über beide Bereiche, wenn es nicht notwendig ist

Die Liste ist sicher unvollständig.

Welchen Unterschied macht dies nun? Findet das in der Übung 3 heraus.


Übungen

1. Schreibe einen Ersatz für ein sehr einfaches (p)grep (nur stdin, keine Parameter) in Python. Hinweis:

Code: Alles auswählen

import sys

inputstream = sys.stdin
Versucht, auf Anzahl Zeichen zu optimieren (abzüglich whitespace)!

Nachtrag: mir ist aufgefallen, dass ich meine Gedanken bezüglich grep-Präfixe/Dialekte vermischt hab. Ich lass das mal für beide Varianten stehen:

a) pgrep (process grep)
b) grep (mit Perl-Dialekt, also wie Python eben arbeitet - darauf wollte ich eigentlich hinaus, dachte an egrep)

Werft eure Lösungen nicht über den Haufen, beides ist interessant. Sorry für die Verwirrung.

2. Suche aus einer Forenübersichtsseite alle Threads heraus und gib sie nach dem Erstellungsdatum sortiert aus, im Format "Titel; Datum; Threadstarter; Anzahl Antworten".

Hinweis für Einsteiger:

Code: Alles auswählen

import requests
import re

# HTML des "Netzwerk"-Unterforums
dfde = requests.get("https://debianforum.de/forum/viewforum.php?f=30").text
# nun hat man alles 💩 in der Hand und kann auf dfde matchen
Danach weiß man auch, warum man auf HTML nicht mit regex arbeiten will :mrgreen: (es geht, aber es ist nicht schön)

3. Folgendes Programm ist nicht so schnell, wie es sein könnte. Seine Aufgabe ist es, aus man-pages die Sektionen auszugeben. Korrekt ist für man man also:

['NAME', 'SYNOPSIS', 'DESCRIPTION', 'EXAMPLES', 'OVERVIEW', 'DEFAULTS', 'OPTIONS', 'ENVIRONMENT', 'FILES', 'HISTORY', 'BUGS']

Das Programm: NoPaste-Eintrag41709

Die Funktion timeit berechnet die Laufzeit einer Funktion über x Durchläufe, hier 100. Finde heraus, wo hier die Performance hakt und wieso so viele andere Worte ausgegeben werden. Hinweis: es gibt große und kleine Versäumnisse, auch inhaltlich. Auf meinem Pi 3 beginnt die Laufzeit bei 22 Sekunden und beträgt nach Optimierung etwa 0,4 Sekunden (auf meinem Desktop-Rechner von 2 auf 0.025, der Faktor ist also nicht in allen Fällen vergleichbar).

(Es gibt sicher noch genug andere Fehler, die man machen kann - wenn ihr die Performance am Ausdruck selbst verschlechtern könnt, ohne es ins Lächerliche zu ziehen, bin ich auf jene gespannt!)

4. Welche kleine Korrektur hätte für das Eingangsbeispiel genügt, damit es inhaltlich korrekt ist (kurze_woerter)?

Und nun viel Spaß :)
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
paedubucher
Beiträge: 850
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von paedubucher » 12.06.2022 10:50:34

Mich hat die Performance-Aufgabe (3) gereizt, darum hier einen Lösungsvorschlag von mir: NoPaste-Eintrag41711

Die Performance wäre evtl. noch verbesserungsfähig, aber den Code habe ich dafür einigermassen elegant hinbekommen (mit List Comprehensions). Ginge wohl noch schneller, aber kaum kürzer. (Beim split()-Aufruf habe ich ebenfalls noch etwas angepasst in der main()-Funktion.)

Das HTML per Regexp parsen würde bei mir Flashbacks auslösen, was ich mir für diesen schönen Sonntag lieber ersparen möchte :P
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
Meillo
Moderator
Beiträge: 8782
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von Meillo » 12.06.2022 12:08:29

Toller Kursteil und interessante Aufgaben! :THX:
Use ed once in a while!

Benutzeravatar
paedubucher
Beiträge: 850
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von paedubucher » 12.06.2022 12:16:04

Meillo hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 12:08:29
Toller Kursteil und interessante Aufgaben! :THX:
Dem möchte ich hiermit noch öffentlich beipflichten. Ich programmiere beruflich Python, verwende aber selten Regexp darin. Ich konnte darum hier wirklich etwas Neues dazulernen. :THX:
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Huo
Beiträge: 605
Registriert: 26.11.2017 14:03:31
Wohnort: Freiburg

Re: RegExp Kurs: Python

Beitrag von Huo » 12.06.2022 16:53:22

Für mich als Hobby-Python-Coder ohne professionellen Hintergrund ist diese Kurseinheit eine tolle Fundgrube an neuem Wissen :THX: , auch wenn sie streckenweise meinen Horizont eindeutig übersteigt :oops: .

Schwierigkeiten, die sich vermutlich klären lassen, habe ich mit dem Abschnitt "raw-Strings". Dort heißt es:
TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
Will ich also auf einen Zeilenumbruch matchen, bleibt mir die Wahl zwischen "\\n" und r"\n".
Das finde ich nicht bestätigt. Ich kann sowohl ohne als auch mit dem r-Prefix den Zeilenumbruch matchen, ohne den Backslash verdoppeln zu müssen:

Code: Alles auswählen

>>> re.findall("\n", "Hallo\nWelt")
['\n']
>>> re.findall(r"\n", "Hallo\nWelt")
['\n']
In der puren Modul-Version des regulären Ausdrucks, matched übrigens auch "\\n" den Zeilenumbruch, in der raw-Version nicht:

Code: Alles auswählen

>>> re.findall("\\n", "Hallo\nWelt")
['\n']
>>> re.findall(r"\\n", "Hallo\nWelt")
[]
Wenn ich es richtig sehe, fangen die Vorteile eines raw-Strings erst an, wenn literale Backslashes gematcht werden sollen. Im folgenden Beispiel erspart er mir immerhin zwei Backslashes.

Code: Alles auswählen

>>> re.findall(r"\\n", "Hallo\\nWelt")
['\\n']
>>> re.findall("\\n", "Hallo\\nWelt")
[]
>>> re.findall("\\\\n", "Hallo\\nWelt")
['\\n']

Benutzeravatar
tegula
Beiträge: 439
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von tegula » 15.06.2022 00:42:28

TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
1. Schreibe einen Ersatz für ein sehr einfaches (p)grep (nur stdin, keine Parameter) in Python. Hinweis:

Code: Alles auswählen

import sys

inputstream = sys.stdin
Versucht, auf Anzahl Zeichen zu optimieren (abzüglich whitespace)!

Nachtrag: mir ist aufgefallen, dass ich meine Gedanken bezüglich grep-Präfixe/Dialekte vermischt hab. Ich lass das mal für beide Varianten stehen:

a) pgrep (process grep)
b) grep (mit Perl-Dialekt, also wie Python eben arbeitet - darauf wollte ich eigentlich hinaus, dachte an egrep)
Script: NoPaste-Eintrag41712.
Ausgabe: NoPaste-Eintrag41716.
TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
2. Suche aus einer Forenübersichtsseite alle Threads heraus und gib sie nach dem Erstellungsdatum sortiert aus, im Format "Titel; Datum; Threadstarter; Anzahl Antworten".
Script: NP 41721
Ausgabe: NP 41719
Script (korrigiert): NoPaste-Eintrag41724
Ausgabe (des korrigierten Scripts): NoPaste-Eintrag41726

TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
3. Folgendes Programm ist nicht so schnell, wie es sein könnte. Seine Aufgabe ist es, aus man-pages die Sektionen auszugeben. Korrekt ist für man man also:

['NAME', 'SYNOPSIS', 'DESCRIPTION', 'EXAMPLES', 'OVERVIEW', 'DEFAULTS', 'OPTIONS', 'ENVIRONMENT', 'FILES', 'HISTORY', 'BUGS']

Das Programm: NoPaste-Eintrag41709
Hier muss ich passen. Die Aufgabe ist für mich zu schwierig :oops:.
TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
4. Welche kleine Korrektur hätte für das Eingangsbeispiel genügt, damit es inhaltlich korrekt ist (kurze_woerter)?
Zum Beispiel, indem man den RE um die Angabe ergänzt, dass die Zeichenfolge zwingend mit einer Wortgrenze beginnen und enden muss.

Code: Alles auswählen

# Korrektur bzw. Änderungsvorschlag
kurze_woerter_veraendert = re.findall(r"\b[A-Za-z]{1,4}\b", text)
## Ergebnis des Änderungsvorschlags anzeigen
print("=======ANWENDUNG DES KORREGIERTEN RE (LÖSUNGSVERSUCH) ======")
print(f"Eingabe: <<{text}>>")
print(f"Anzahl der extrahierten Zeichenfolgen: {len(kurze_woerter_veraendert)}")
print("\n**Extrahiert wurden folgende Zeichenfolgen:**")
for n,i in enumerate(kurze_woerter_veraendert):
    print("Match Nr.%d) \t %s" % ((n+1),i))
print("============================================================")
Script: NoPaste-Eintrag41717
Ausgabe: NoPaste-Eintrag41718

EDIT (15.06.2022 um 10:42 Uhr): Aufgabe 2 -->Befehlsfolge (bzw. einen darin enthaltenen RE) für Erhebung von datum_zuletzt korrigiert. ALT (fehlerhaft): Es wurde nur die Uhrzeit erhoben. NEU (korrekt): Es wird sowohl die Uhrzeit (z. B. "07:35:02 ") als auch das eigentliche Datum (z. B. "15.06.2022") erhoben.
Zuletzt geändert von tegula am 15.06.2022 10:52:39, insgesamt 2-mal geändert.

Benutzeravatar
TRex
Moderator
Beiträge: 8038
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

Re: RegExp Kurs: Python

Beitrag von TRex » 15.06.2022 07:35:02

paedubucher hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 10:50:34
Ginge wohl noch schneller, aber kaum kürzer.
Doch, beides :) Meine Musterlösung hat etwa 100 Zeichen weniger, und sieht gar nicht so unähnlich aus. Für den Rest musst du hinterfragen, was ich zur Performance geschrieben habe - nicht alles gilt immer uneingeschränkt. Vielleicht ist ein anderer Weg besser, der weniger effizient aussieht, es aber nicht ist.
Huo hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 16:53:22
Schwierigkeiten, die sich vermutlich klären lassen, habe ich mit dem Abschnitt "raw-Strings". Dort heißt es:
TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
Will ich also auf einen Zeilenumbruch matchen, bleibt mir die Wahl zwischen "\\n" und r"\n".
Das finde ich nicht bestätigt. Ich kann sowohl ohne als auch mit dem r-Prefix den Zeilenumbruch matchen, ohne den Backslash verdoppeln zu müssen:
Das muss ich mir nochmal genauer anschauen, vielleicht ist der Zeilenumbruch ein blödes Beispiel und wird gesondert behandelt... danke für den Hinweis.
tegula hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 00:42:28
Script: 41721
Ausgabe: 41719
pandas zur Ausgabeformatierung :lol: kreative Idee. Du könntest übrigens auf die sub verzichten, wenn du den Inhalt zwischen den Tags beim search() jeweils in eine Gruppe packst und dir die holst (statt dem ganzen match).
tegula hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 00:42:28
Hier muss ich passen. Die Aufgabe ist für mich zu schwierig :oops: .
Sie läuft dir ja nicht weg. Vielleicht findest du bei anderen Lösungen Hilfestellung :)
tegula hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 00:42:28
Zum Beispiel, indem man den RE um die Angabe ergänzt, dass die Zeichenfolge zwingend mit einer Wortgrenze beginnen und enden muss.
Genau daran dachte ich auch. Ist vermutlich die effizienteste Lösung, wenn auch nicht die einzige.
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

tobo
Beiträge: 1964
Registriert: 10.12.2008 10:51:41

Re: RegExp Kurs: Python

Beitrag von tobo » 15.06.2022 11:24:00

TRex hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 07:35:02
Huo hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 16:53:22
Schwierigkeiten, die sich vermutlich klären lassen, habe ich mit dem Abschnitt "raw-Strings". Dort heißt es:
TRex hat geschrieben: ↑ zum Beitrag ↑
12.06.2022 09:06:33
Will ich also auf einen Zeilenumbruch matchen, bleibt mir die Wahl zwischen "\\n" und r"\n".
Das finde ich nicht bestätigt. Ich kann sowohl ohne als auch mit dem r-Prefix den Zeilenumbruch matchen, ohne den Backslash verdoppeln zu müssen:
Das muss ich mir nochmal genauer anschauen, vielleicht ist der Zeilenumbruch ein blödes Beispiel und wird gesondert behandelt... danke für den Hinweis.
Gruppierungen (die Rückwärtsreferenz darauf) und ' werden noch nicht erkannt:
https://docs.python.org/3.9/library/re.html hat geschrieben: Most of the standard escapes supported by Python string literals are also accepted by the regular expression parser:

\a \b \f \n
\N \r \t \u
\U \v \x \\

(Note that \b is used to represent word boundaries, and means “backspace” only inside character classes.)

'\u', '\U', and '\N' escape sequences are only recognized in Unicode patterns. In bytes patterns they are errors. Unknown escapes of ASCII letters are reserved for future use and treated as errors.

Octal escapes are included in a limited form. If the first digit is a 0, or if there are three octal digits, it is considered an octal escape. Otherwise, it is a group reference. As for string literals, octal escapes are always at most three digits in length.

Changed in version 3.3: The '\u' and '\U' escape sequences have been added.

Changed in version 3.6: Unknown escapes consisting of '\' and an ASCII letter now are errors.

Changed in version 3.8: The '\N{name}' escape sequence has been added. As in string literals, it expands to the named Unicode character (e.g. '\N{EM DASH}').

Huo
Beiträge: 605
Registriert: 26.11.2017 14:03:31
Wohnort: Freiburg

Re: RegExp Kurs: Python

Beitrag von Huo » 15.06.2022 13:25:13

Für mich als Hobby-Coder sind die Programmieraufgaben schon sehr anspruchsvoll bzw. wären zeitaufwendig. Zu Aufgabe (1) kann ich einen einfachen Lösungsversuch anbieten:
NoPaste-Eintrag41727

Anwendungsbeispiel (Suche aller Reime auf "er" im Gedicht "Schwaebische Kunde"):

Code: Alles auswählen

$ grep.py "er\W*$" < schwaebische-kunde.txt
Da musst' er mit dem frommen Heer
Durch ein Gebirge, wuest und leer.
Da sprengten ploetzlich in die Queer
Fuenfzig tuerkische Reiter daher,
EDIT: Vordefinierte Zeichenklassenbereiche wie [:punct:] kennt Python offenbar nicht? Habe im Beispiel deshalb ersatzweise "\W" verwendet.

Benutzeravatar
Meillo
Moderator
Beiträge: 8782
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von Meillo » 15.06.2022 13:40:53

Huo hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 13:25:13
Für mich als Hobby-Coder sind die Programmieraufgaben schon sehr anspruchsvoll bzw. wären zeitaufwendig.
Die Idee bei den Programmiersprachen-Einheiten war, dass in erster Linie diejenigen im Fokus liegen sollen, die die jeweilige Sprache noch beherrschen, darin aber noch keine REs nutzen. Waehrend die bisherigen Teile sehr tief angefangen haben und dann einen schrittweisen Lernprozess abgebildet haben, springen wir derzeit von Sprache zu Sprache auf jeweils hohe Stufen. Es ist demnach nicht mehr gleichermassen realistisch alle Auigaben durcharbeiten zu koennen wie bisher. Trotzdem denke ich, dass man einiges lernen kann wenn man sich anschaut wie man REs in verschiedenen Sprachen nutzt. Das wird natuerlich wertvoller wenn wir mehr Sprachen haben, die in den naechsten Wochen nach und nach kommen.

Irgendwann spaeter wird es sicherlich auch wieder eine aufbauende Serie von Kursteilen geben, dann zu BREs (mit sed) und PCREs (mit modernen Scriptsprachen) ... ich glaube, wir koennen noch die Wochen eines weiteren Jahres fuellen, alleine mit dem Thema Regulaere Ausdruecke. :-D


Aber um nochmal auf deine urspruengliche Aussage zurueck zu kommen: Wie koennten einfachere Aufgaben bei diesem Thema aussehen? Wir koennten nun ja gemeinsam ein paar weitere Aufgaben formulieren, die auch von Hobby-Programmierern (mit Python) geloest werden koennen.
Use ed once in a while!

Huo
Beiträge: 605
Registriert: 26.11.2017 14:03:31
Wohnort: Freiburg

Re: RegExp Kurs: Python

Beitrag von Huo » 15.06.2022 14:34:58

Meillo hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 13:40:53
Aber um nochmal auf deine urspruengliche Aussage zurueck zu kommen: Wie koennten einfachere Aufgaben bei diesem Thema aussehen? Wir koennten nun ja gemeinsam ein paar weitere Aufgaben formulieren, die auch von Hobby-Programmierern (mit Python) geloest werden koennen.
Um Himmels willen, meine Aussage sollte keinesfalls den Wunsch nach einfacheren Aufgaben implizieren, von deren sinnvollen Aussehen ich auch keine Vorstellung hätte. Wer kann überhaupt sagen, wie viele Hobby-Programmierer neben mir am Regexp-Kurs teilnehmen? :wink: Ich habe auch so viel von dieser Kurseinheit profitiert, manches im interaktiven Python-Interpreter ausprobiert und bei der Bearbeitung von Aufgabe (1) einiges gelernt. :THX:

Benutzeravatar
Meillo
Moderator
Beiträge: 8782
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von Meillo » 16.06.2022 08:11:56

Huo hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 14:34:58
Meillo hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 13:40:53
Aber um nochmal auf deine urspruengliche Aussage zurueck zu kommen: Wie koennten einfachere Aufgaben bei diesem Thema aussehen? Wir koennten nun ja gemeinsam ein paar weitere Aufgaben formulieren, die auch von Hobby-Programmierern (mit Python) geloest werden koennen.
Um Himmels willen, meine Aussage sollte keinesfalls den Wunsch nach einfacheren Aufgaben implizieren, von deren sinnvollen Aussehen ich auch keine Vorstellung hätte. Wer kann überhaupt sagen, wie viele Hobby-Programmierer neben mir am Regexp-Kurs teilnehmen? :wink:
Ich habe deinen Kommentar gar nicht kritisch verstanden und meine Antwort auch nicht bloss auf dich bezogen. Es war mehr eine offene Frage, ob wir den Lernerfolg fuer alle verbessern koennen indem wir zusaetzlich vielleicht noch andere Fragen stellen, an die bisher noch niemand gedacht hat. ;-)
Use ed once in a while!

TuxPeter
Beiträge: 1954
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von TuxPeter » 16.06.2022 10:12:06

Was mich betrifft, so finde ich es wirklich sehr interessant, einen Einblick in Python zu bekommen und bin dankbar, das hier in einem lebendigen Prozess tun zu können; was die REs selber betrifft, so habe ich für mich beschlossen, mich auf die Dinge zu konzentrierten, die ich selber anzuwenden gedenke. Es gibt so viele Baustellen, nicht nur im Reiche der Bits'n Bytes, die alle betreut werden wollen. ...

Benutzeravatar
tegula
Beiträge: 439
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von tegula » 16.06.2022 19:49:38

TRex hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 07:35:02
tegula hat geschrieben: ↑ zum Beitrag ↑
15.06.2022 00:42:28
Du könntest übrigens auf die sub verzichten, wenn du den Inhalt zwischen den Tags beim search() jeweils in eine Gruppe packst und dir die holst (statt dem ganzen match).
Danke für deinen Hinweis :THX:
Hab mein Script zu Aufhgabe 2 jetzt noch mal überarbeitet. Folgendes habe ich geändert:
  • re.sub() wird nicht mehr verwendet, stattdessen werden, wie von dir empfohlen, Gruppen/Unterausdrücken genutzt
  • die URL wird nicht mehr manuell in das Script eingetragen, sondern als Komandozeilenargument beim Aufruf des Skripts übergeben
  • der Aufbau der URL wird überprüft.
  • die Ausgabe ist jetzt nach dem Erstellungsdatum der Threads sortiert (diesen Bestandteil der Aufgabe hatte ich bei meinem ursprünglich Skript vergessen :oops:)
Script: NoPaste-Eintrag41728
Aufruf:: NoPaste-Eintrag41729
Ausgabe (am Beispiel des Forums "Neuigkeiten rund um debianforum.de"): NoPaste-Eintrag41730

Benutzeravatar
paedubucher
Beiträge: 850
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von paedubucher » 20.06.2022 21:16:58

Ich habe jetzt noch an der Thread-Extraktion (Übung 2) gearbeitet: NoPaste-Eintrag41743

Aus traumabewältigungstechnischen Gründen habe ich nur den Inhalt einzelner Zeilen per Regexp extrahiert. Den Beginn eines Threads im HTML-Text finde ich mit einer einfachen Regexp heraus. Die relevanten Zeilen ermittle ich dann anhand ihrer Zeilennummer im Abschnitt.

Es funktioniert nicht alles 100%-ig, aber das reicht mir mal für eine Stunde basteln.
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
tegula
Beiträge: 439
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von tegula » 21.06.2022 19:47:32

paedubucher hat geschrieben: ↑ zum Beitrag ↑
20.06.2022 21:16:58
Ich habe jetzt noch an der Thread-Extraktion (Übung 2) gearbeitet: NoPaste-Eintrag41743
Cool :THX:

Mir ist aufgefallen, dass wir die Anzahl der Antworten aus unterschiedlichen Textstellen ziehen. Das Ergebnis bleibt aber dasselbe. Die Anzahl der Antworten scheint auf den Forenübersichtsseiten doppelt hinterlegt zu sein 8O.

paedubucher (NoPaste-Eintrag41743, Zeile 38):

Code: Alles auswählen

answers_re = re.compile(r'<strong>([0-9]+)</strong>')
tegula (NoPaste-Eintrag41728, Zeile 89-90):

Code: Alles auswählen

anzahl_antworten = re.findall(pattern="class=\"posts\">(\d+)",
			      string=dfde_forum)

Benutzeravatar
TRex
Moderator
Beiträge: 8038
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

Re: RegExp Kurs: Python

Beitrag von TRex » 25.06.2022 09:16:40

So... für alle Interessierten, hier sind meine Lösungen:

1. grep: NoPaste-Eintrag41748

Ich glaube, da gibts nicht viel zu sagen.

2. Threads extrahieren: NoPaste-Eintrag41749 und wie ich es sonst gemacht hätte NoPaste-Eintrag41750

Hier hatte paedubucher natürlich völlig Recht, während ich das Parsen von kleineren Fetzen HTML durchaus für legitim halte, gilt im Großen und Ganzen dieser stackoverflow-Beitrag: https://stackoverflow.com/questions/173 ... ained-tags
Kurz: es ist nicht sinnvoll, weil fragil und nervenaufreibend. Was die Performance angeht, habe ich es mit meinem üblichen Vorgehen verglichen (beautifulsoup, ein DOM-Parser) - gibt sich zeitlich nichts. Aber es sieht besser aus und hält vermutlich länger ;)

3. Performance:

original: NoPaste-Eintrag41751
besser: NoPaste-Eintrag41752
gut: NoPaste-Eintrag41753

Resultate auf nem Pi 3B (main-Aufruf auskommentiert):

Code: Alles auswählen

$ for perftest in 03_perf_*; do echo $perftest; python3 $perftest; done
03_perf_bad.py
Durchschnittliche Laufzeit in Sekunden: 22.293378463946283
03_perf_okayish.py
Durchschnittliche Laufzeit in Sekunden: 0.5714229950681329
03_perf_fine.py
Durchschnittliche Laufzeit in Sekunden: 0.2065923297777772
Ich hatte gehofft, dass es noch ein paar kreative Köpfe aus dem lokalen Minimum schaffen, welches ich da als Falle platziert habe. Es ist nämlich tatsächlich schneller, (spoiler, markier mich) das re.MULTILINE-Flag zu aktivieren und den ganzen Text auf einmal zu parsen als über die Zeilen zu iterieren.(/spoiler) Ansonsten klar, nichts doppelt machen und mehrfach verwendete Ausdrücke kompilieren. Ausstehend meinerseits ist noch ein Vergleich mit go.

4. Bug im intro

Hat tegula bereits beantwortet: \b außenrum. Es gibt schlechtere Lösungen mit whitespace und Gruppenkonstrukten, aber wollen kann das keiner.
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
tegula
Beiträge: 439
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von tegula » 25.06.2022 11:26:02

Danke für den tollen Kurs-Part und die Musterlösung :THX:

OT: Mir ist aufgefallen, dass du (TRex) und paedubucher das meiste in eine oder mehrere Funktionen ausgelagert habt, während bei mir alles im jeweiligen Hauptprogramm steht. Sollte ich als Anfänger auch schon so vorgehen? Oder ist das erst bei mehr Erfahrung mit Python sinnvoll? Gibt es eine (Faust-)Regel, nach der ich entscheiden kann, was ins Hauptprogramm kommt und was in eine Funktion gehört?

TuxPeter
Beiträge: 1954
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von TuxPeter » 25.06.2022 12:46:50

Ich bin zwar nicht gefragt, aber möchte mich trotzdem dazu äußern: Ich finde es völlig OK, immer möglichst kleinschrittig vorzugehen. Das ist gut für das Verständnis, die Fehlersuche sowie für die Weiterentwicklung. Jedenfalls vielen Dank für den Python-Einblick.

Ansonsten gibt's heute abend oder morgen früh eine kleine Schippe Pascal mit nicht allzu schwierigen Aufgaben. Aber Ihr könnt Euch dann ja auch selber bessere oder schwierigere machen. Bis dahin:

MfG,
TuxPeter

Benutzeravatar
TRex
Moderator
Beiträge: 8038
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

Re: RegExp Kurs: Python

Beitrag von TRex » 25.06.2022 12:49:39

tegula hat geschrieben: ↑ zum Beitrag ↑
25.06.2022 11:26:02
OT: Mir ist aufgefallen, dass du (TRex) und paedubucher das meiste in eine oder mehrere Funktionen ausgelagert habt, während bei mir alles im jeweiligen Hauptprogramm steht. Sollte ich als Anfänger auch schon so vorgehen? Oder ist das erst bei mehr Erfahrung mit Python sinnvoll? Gibt es eine (Faust-)Regel, nach der ich entscheiden kann, was ins Hauptprogramm kommt und was in eine Funktion gehört?

Das ist eine dieser Fragen, die es durchaus in stoischer Exaktheit beantwortet gibt (es schimpft sich clean code) und deren Beantwortung nicht unbedingt zu besserem Code führt.

Du musst dir vor Augen führen, warum du etwas modularisierst. In diesem Fall waren es die Ausführung als demo und die Zeitmessung in Übung 3. Da wollte ich ein paar Beispiele vorführen und gleichzeitig aber vergleichbar und ohne Seiteneffekte den eigentlichen Kern auf Laufzeit testen - da ist Ausgabe im Terminal und die Ausführung von "man" eher im Weg.
Außerdem könnte der Code sehr lang sein - dann sucht man auch nach einem Weg, funktionale Blöcke zu finden. Wenn du deinen Blöcken verständliche Namen geben kannst und der Inhalt eine gewisse "Schaffenshöhe" erreicht hat, hast du den Sweet Spot erreicht (meiner Meinung nach).

Wenn du Tests für deine Funktionalität schreibst, ergibt sich das übrigens fast von alleine. Und danach kannst du dir die Regeln zu Clean Code anschauen und überlegen, wie du deinen Code für andere noch besser verständlich machst :)
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
tegula
Beiträge: 439
Registriert: 04.06.2004 13:51:04
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von tegula » 25.06.2022 13:02:35

Danke! :THX:

Benutzeravatar
Meillo
Moderator
Beiträge: 8782
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: RegExp Kurs: Python

Beitrag von Meillo » 25.06.2022 14:32:57

TRex hat geschrieben: ↑ zum Beitrag ↑
25.06.2022 12:49:39
Das ist eine dieser Fragen, die es durchaus in stoischer Exaktheit beantwortet gibt (es schimpft sich clean code) und deren Beantwortung nicht unbedingt zu besserem Code führt.
:THX:

Besserer Code wird es dann wenn man seinen eigenen Kopf einschaltet (das hat tegula schonmal getan wodurch er auf die Frage gekommen ist). Die entscheidende Frage ist nicht *wie* soll ich es machen, sondern *warum* mache ich es so wie ich es mache. Generelle Antworten koennen nie zuverlaessig die richtigen sein.

Die beste Optimierung ist IMO fuer die Lesbarkeit ... und was lesbarer Code ist kann man nur herausfinden wenn man fremden Code anschaut und vergleicht. Den den man schnell versteht und nicht ueberrascht wird, das ist lesbarer Code (in der Programmierkultur, die man kennt). Durch Vergleich findet man heraus worin er sich von unlesbarem Code unterscheidet.

Wie man Code strukturiert und untergliedert haengt mit dem Gesamtsystem zusammen. Wenn du einen 50-Zeiler zum Ausprobieren und Wegwerfen schreibst, dann brauchst du Funktionen viel weniger wie wenn er die Ausgangsbasis fuer ein daraus erwachsendes groesseres Programm ist. IMO sollte man nicht zu frueh und nicht zu spaet modularisieren, sondern an dem Punkt wo es relevant zu werden beginnt. Zu frueh waere es unnoetiger Overhead (z.B. typisches Java-Overengineering fuer Miniprogramme); zu spaet sinkt die Erweiterbarkeit und steigt der Aufwand. Diese Ueberlegung ist voellig natuerlich wenn man Programmieren nicht als Wasserfall oder Maurertaetigkeit versteht, sondern als Garten, den man kontinuierlich pflegt und umarrangiert.

Darum laesst sich IMO die Frage, ob diese Programme hier mit oder ohne Teilfunktionen besser sind, nicht pauschal beantworten, weil die Antwort davon abhaengt wie die Situation um das Programm herum aussieht ... die Vorgeschichte, der erwartete Zukunftsweg, usw. ...

Zu Clean Code ist meine Meinung wohl schon bekannt: Clean Code ist ein Kult, also eine Form des Nichtdenkens, und damit das Gegenteil von dem was noetig ist um guten Code zu schreiben. Ein weit besserer Weg zu gutem Code ist es viel fremden Code zu lesen und zwar aus verschiedenen Programmierkulturen. Natuerlich ist das viel Aufwand, aber die Frage, was guter Code ist, ist eben auch eine sehr anspruchsvolle. ;-)


Um nochmal konkret zu werden: Es koennen verschiedene Faustregeln Sinn machen, u.a.:

- Eine Funktion bzw. der globale Code sollte nicht laenger als eine oder zwei Bildschirmseiten sein.
- Der Name der Funktion sollte spezifisch sein und den kompletten Inhalt der Funktion abdecken. Wenn das nicht moeglich ist, dann sollte man aufteilen.
- Wenn man Code wiederholt koennte das Auslagern in Funktionen sinnvoll sein.
- Wenn die Verschachtelungstiefe zu gross wird. (Im Falle von C-Code und den dort ueblichen Bezeichnerlaengen merkt man es wenn bei 8-Zeichen-Tabs in einem 80-Zeichen-Terminal der Platz ausgeht. In anderen Sprachen braucht man andere Merkhilfen.)
- Wenn man zu viel um einanderscrollen muss, ist vermutlich zu viel Code in einer Funktion. Andernfalls koennte man direkt zu Funktionen springen, bzw. muss das gar nicht weil ihr Name bereits aussagekraeftig genug ist.

All da sind Anhaltspunkte (und es kann weitere geben) an denen man sich orientieren kann.


Tendenziell wuerde ich mit weniger Funktionen starten und bei wachsendem Code welche einbauen indem ich den bisherigen Code umbaue. Auf diesem Weg vermeide ich Overengineering, was ich schlimmer finde als zu lange Funktionen.

Statt sich viel Gedanken zu machen, wie der Code richtig organisiert sein sollte, bevor man ihn schreibt, wuerde ich viel Ueben Code umzubauen, dann kann man die Entscheidung naemlich auch spaeter noch treffen, wenn man den Code schon geschrieben hat. Zudem ist die Faehigkeit, Code gut umbauen zu koennen, immer hilfreich.


Tut mir leid fuer die laengeren Ausfuehrungen ... bei dem Thema konnte ich einfach nicht widerstehen. :-D (Falls es ausartet kann das gerne abgetrennt werden.)
Use ed once in a while!

TuxPeter
Beiträge: 1954
Registriert: 19.11.2008 20:39:02
Lizenz eigener Beiträge: MIT Lizenz

Re: RegExp Kurs: Python

Beitrag von TuxPeter » 25.06.2022 16:15:44

Da möchte ich auch noch mal ...

Es hängt letztlich von der Aufgabenstellung ab. Was den Programmierstil betrifft, so wurzeln meine Erfahrungen tief im letzten Jahrtausend, als kaufmännischer Anwendungsprogrammierer hatte ich zunächst mit dem Änderungsdienst bereits gut abgehangener und von mehreren Generationen unterschiedlichst begabter Junior-Programmierer heftig durchgerührter Programmpakete zu tun. Da wusste man oft, dass man beispielsweise zum nächsten unaufschiebbaren Produktiv-Programmlauf nur fertig werden konnte, wenn man an X verschiedenen Stellen einbaute: Wenn diese Nummer dann "PERFORM MIST THROUGH EX-MIST". (Es handelte sich um COBOL) Und diese Stellen waren regelmäßig ganz in der Nähe der Routinen, wo das Band (sequentiell, versteht sich) eingelesen oder "gemerged" wurde, weil das die einzigen Stellen waren, wo man sicher war, dass die Daten noch "zu haben" waren. Ansonsten hatte ich - natürlich war GOTO verpönt - dann schon öfter in Stellen mich festgebissen, die durch ein beherztes GOTO DRUMHERUM vom Vorgänger für ein totes Code-Nest gesorgt hatte. Andrerseits gab es eine schöne FIBU im Haus, die den gesamten Kontenrahmen als "Dispatcher" durch ein -zig fach verschachteltes IF THEN ELSE, rein optisch auf den damaligen Tabellierpapier-Listen nett anzuschauen, mit GOTO abarbeitete. Es war schon fein, diesem gleichzeitig langweiligen und anstrengenden Wahnsinn den Rücken zu kehren zu können.

Später habe ich werdenden Kaufleuten, die nicht unbedingt viel damit am Hut haben wollten, COBOL, Basic, Office-Programme, und, was für mich am nettesten war, Pascal beigebracht bzw. zu bringen versucht. Nur einige wenige davon sind beim Programmieren gelandet. Aber von daher rührt meine Faustregel: So kleinschrittig wie möglich.

Was die ernsthaftere Programmierung betrifft, so musste ich eigentlich nur so schreiben, dass ich mich selber nach angemessener Zeit noch verstand. In Pascal habe ich (noch unter DOS) Programme geschrieben, mit denen ich auch gewerblich einen gewissen Erfolg hatte, ich hatte dann einen gewissen "Schatz" an Units, mit denen ich sehr schnell Prototypen zusammenschustern konnte und schnell auf Änderungs- und Erweiterungswünsche seitens der werten Kundschaft reagieren konnte. Diese Schiene habe ich dann verlassen, und dann von dem ganzen objektorientierten Paradigma eigentlich nichts mehr mitbekommen.

Allerdings stelle ich mir gerne vor, "Objekte" bereits über die sog. Procedure-Variablen für mich erfunden zu haben, indem diesen zur Laufzeit datengesteuert verschiedene konkrete Proceduren zugewiesen wurden. Beispiel: Kaufmännische "Listen", Abschlüsse, Seiten- und mehrstufiger Gruppenwechsel, und dann eine Änderung / Erweiterung: Einfach eine zusätzliche / andere Procedure einklinken.

Was unterscheidet Systeme, die für sich selbst, für den Eigengebrauch, von solchen, die zum Verständnis auch für andere geschrieben werden? Ich glaube, im Grund nichts, denn wenn man in einem Jahr oder so irgendwas wieder verstehen will, dann muss man genauso verständlich wie für andere schreiben.

Vor kurzem habe ich entdeckt, wieviel Spaß es macht, Arduinos zu programmieren. Dazu musste ich mich ein wenig in C bzw. C++ reinschaffen, und auch hier finde ich, dass sich die Gliederung in Unterprogramme und deren Größe von der jeweiligen Aufgabenstellung der Controller fast von alleine ergeben. Allerdings zwingt die Knappheit der Ressourcen schnell zu Optimierungen, was dann aber auch wieder Spaß machen kann ...

So, damit dieser OT-Roman jetzt nicht zu lang wird, mache ich mal Schluss, möge es ein wenig unterhaltsam sein ...
MfG
TuxPeter

Huo
Beiträge: 605
Registriert: 26.11.2017 14:03:31
Wohnort: Freiburg

Re: RegExp Kurs: Python

Beitrag von Huo » 25.06.2022 17:07:06

TRex hat geschrieben: ↑ zum Beitrag ↑
25.06.2022 09:16:40
So... für alle Interessierten, hier sind meine Lösungen:

1. grep: NoPaste-Eintrag41748

Ich glaube, da gibts nicht viel zu sagen.
Für mich gibt es zu Deiner Lösung zu Aufgabe 1 gleichwohl einige Fragezeichen. :wink:
Deine Lösung will, wie ich sehe, grep -o simulieren (also nicht ganze Zeilen, sondern nur die gematchten Strings ausgeben). Dabei frage ich mich, ob im folgenden Abschnitt

Code: Alles auswählen

while True:
    line=sys.stdin.readline().strip()
    if not line:
        break
    match = regexp.search(line)
    if match:
        print(match.group())
(1) das strip() und die anschließende if-Bedingung mit dem break besser wegfallen sollten, da man erstens eventuell auch Leerzeichen am Anfang oder Ende einer Zeile matchen möchte und zweitens den Durchlauf nicht bei Leerzeilen abbrechen will,
(2) sich hier statt der search- sinnvollerweise die finditer-Funktion anbietet, um wie das "Vorbild" grep -o alle Matches pro Zeile auszugeben statt nur den ersten,
(3) es auch etwas unkomplizierter als mit "while True" und readline geht.

Spräche etwas dagegen, obigen Abschnitt durch den folgenden Dreizeiler zu ersetzen?

Code: Alles auswählen

for line in sys.stdin:
    for match in regexp.finditer(line):
        print(match.group())

Antworten