re.findall() und groupindex

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.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Hi,

ich finde es Klasse, dass python in regulären Ausdrücken benannte Gruppen unterstützt.
Nur stelle ich mir irgendwie zu blöd an.

Code: Alles auswählen

def entferne_duplikate_forum(pcZeichenkette, pcsRegEx):
    return_value = ''
    lvRegEx=re.compile(pcsRegEx)

    lvSet = set()
    lvListe = lvRegEx.findall(pcZeichenkette)

    print "Group", lvRegEx.groupindex
    lvnGroupIndex = lvRegEx.groupindex["INHALT"]
    print 'der Index meiner gesuchten Gruppe ist: ', lvnGroupIndex
    
    for lvElement in lvListe:
        i=0
        for lvZeile in lvElement:
            print 'Zeile ',i, '<', lvZeile, '>'
            i+=1
        lvSet.add(lvElement[lvnGroupIndex].strip())
        #lvSet.add(lvElement[0].strip()) '''so klappt es, mit groupindex nicht'''
        
    lvListe = []
    for lvElement in lvSet:
        lvListe.append(lvElement)
        print 'Set - Element', lvElement
        
    lvListe.sort()
    if lvListe is not None:
        for lvElement in lvListe:

            if not return_value == '':
                return_value += ', '
            return_value += lvElement
        
    return return_value

def test_entferne_duplikate_forum():

    lvsPattern = '((?P<INHALT>) <NULL>\s*|[(][^)]*[)][^(]*)((?P<TRENNER>),|$)'
    lvsCheckItOut = '(Das) 1, (ist) 2, (ein) 3, (Test) 4'
    lvsErgebnis = entferne_duplikate_forum(lvsCheckItOut,lvsPattern)
    print "Ergebnis <",lvsErgebnis,">"
    
test_entferne_duplikate_forum()
Das muss eigentlich rauskommen:
Ergebnis < (Das) 1, (Test) 4, (ein) 3, (ist) 2 >

Das kommt raus
Ergebnis < , >

sieht etwas verwirrend aus, weil da ein Komma drin ist, aber das liegt daran, dass er nun (zufällig?) die Spalte mit den Trennern erwischt hat und da ist halt immer ein Komma drin ...

Wie kann ich das mit der Gruppe machen?
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@NoPy: Dein Code ist absolut unverständlich. Was sollen die ganzen 'lv'- und 'pc'-Präfixe in den Variablennamen? Der Rest der Variablennamen ist dann total unsinnig, ich hab ein Set oder eine Liste, was der Inhalt der Variable ist, kann ich aber aus dem Namen nicht sehen.
Wenn Du einen Index "i" in einer for-Schleife brauchst, dann nimm "enumerate". "lvListe" ist nie None, weshalb das "if" überflüssig ist und statt der for-Schleife suchst Du ', '.join(lvListe).

Zum Problem: Deine regulärer Ausdruck macht nicht das, was Du denkst. Die benannten Gruppen sind alle Leer, weil sie kein Zeichen matchen. Die jeweilige Gruppe ohne Namen, die um diese Gruppen liegen, enthalten das von Dir gewünschte.
Was Du willst ist das: r'(?P<INHALT>\s*<NULL>\s*|\(.*?\)[^(]*)(?P<TRENNER>,|$)'
BlackJack

@NoPy: Diese Präfixe sind gruselig. Falls das `p` für „Parameter” stehen sollte: Wenn es damit Probleme gibt zu erkennen was ein Argument und was ein lokaler Name ist, sofern das überhaupt wichtig ist, dann ist die Funktion ganz einfach zu lang und/oder zu unübersichtlich.

`return_value` wird viel zu früh eingeführt. Das ist ja ”meilenweit” (ca. 20 Zeilen) vor der ersten Verwendung, *da* muss man dann erst mal wieder nach oben suchen womit das überhaupt initialisiert wird.

Was Du da mit `lvnGroupIndex` veranstaltest ist sehr ungewöhnlich. Hätte gar nicht gedacht, dass so etwas überhaupt geht. Man würde normalerweise `finditer()` statt `findall()` nehmen und die Gruppe dann von dem jeweiligen Match-Objekt abfragen. Dann braucht man den regulären Ausdruck auch nicht kompilieren und spart sich eine lokale Variable. Und die erste Verwendung von `lvListe` wird dann auch hinfällig weil man da direkt über das Ergebnis von `finditer()` iterieren kann. Ist sowieso unschön den selben lokalen Namen für verschiedene Listeninhalte zu verwenden.

Wenn man die ganzen Debugging-Ausgaben rauswirft, kann man aus den ersten vierzehn Zeilen der Funktion vier machen. Ein `set()`-Aufruf mit einem Generatorausdruck der die 'INHALT'-Gruppen aus der Zeichenkette holt.

Hier mal das Programm in Python :-):

Code: Alles auswählen

import re


def entferne_duplikate_forum(string, regular_expression):
    return ', '.join(
        sorted(
            set(
                match.group('INHALT').strip()
                for match in re.finditer(regular_expression, string)
            )    
        )
    )


def test_entferne_duplikate_forum():
    pattern = r'(?P<INHALT> <NULL>\s*|[(][^)]*[)][^(]*)(?P<TRENNER>,|$)'
    source = '(Das) 1, (ist) 2, (ein) 3, (Test) 4'
    result = entferne_duplikate_forum(source, pattern)
    print 'Ergebnis', repr(result)


if __name__ == '__main__':   
    test_entferne_duplikate_forum()
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

ah, danke. Nun ist sowohl klar, was er tat als auch, was ich falsch gemacht habe.

zu meinen Präfixen: Die meiste Zeit schreibe ich anderen Code und nicht python. Für mich ist es leichter, den Text SO zu lesen (und zu benutzen). Ich bemühe mich aber wenigstens, die Konventionen bezüglich der Schnittstellen einzuhalten. (Funktionsnamen, Klassennamen etc.)
Zu mehr bin ich im Moment nicht bereit, denn dann versteh ich nicht mehr, was da ist.

@BlackJack: Ich kann den Code, den Du geschrieben hast, verstehen, aber Du glaubst doch nicht, dass ich im Moment in der Lage bin, ihn zu schreiben, oder? Ich bin seit 3 Wochen relativ intensiv bei python, wenn das Projekt durch ist, werde ich den Code noch aufräumen und kommentieren und dann wahrscheinlich über Monate nichts mehr mit python zu tun haben. Natürlich versuche ich, es so zu tun, wie ihr es für richtig haltet, aber nicht in einem Schritt. Ich initialisiere Variablen gern vor der ersten Verwendung (z.B. return value) und da ich mich an meine eigenen Gepflogenheiten zu halten pflege, weiß ich auch immer, was da drin steht/stehen kann.

@sirius: Auch Dir gebührt Dank. es ist jetzt alles klar. python ist für mich per se zu unübersichtlich. Daher erleichtert es MIR die Sache schon, wenn ich weiß, ob ich gerade etwas benutze, was ich übergeben bekommen habe, oder das, was die Funktion sich aufbereitet hat. Das die Variablen in diesem Beispiel so klingende Namen haben, wie Liste und Set ist dem geschuldet, dass sie darüberhinaus keine Bedeutung haben. Alle passenden Einträge werden mittels regulärem Ausdruck in die Liste eingetragen, von dort in ein Set geschaufelt, um Duplikate zu eliminieren, von dort in eine Liste, damit ich sie sortieren kann und von da in einen String, der zurückwandert. Inhaltlich macht ihr nichts anderes, nur, dass ihr pythontypische Verkürzungsschreibweisen benutzt, die mir NOCH nicht geläufig sind.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Doch noch mal ein paar Nachfragen

Code: Alles auswählen

def entferne_duplikate_forum(string, regular_expression):
    return ', '.join(
    # 4. Ich sage der Zeichenkette ",", sie möge sich zwischen die Elemente der 
    # übergebenen Gruppe (set, Tupel, Array?) schieben 
    # und das Ergebnis auswerfen.
    # richtig?
        sorted(
        #3. Wo kommt diese Funktion her? Gibt es eine "sorted- Funktion, die eine setz bekommen kann?
        #   wie ist das zu interpretieren?
            set(
            #2. das ist der Konstruktor für ein set, der successive mehrere Werte bekommt
            #   richtig?
                match.group('INHALT').strip()
                for match in re.finditer(regular_expression, string) 
                #1. das ist eine dieser python- typischen Umkehrungen
                #   statt for x ..: x -> x for x ...
                # und alle "match"- instanzen werden dann an den aufrufenden set- Konstruktor übergeben
                # richtig?
            )    
        )
    )
Insbesondere Punkt 3 verstehe ich gar nicht.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

1. Nein, das ist nicht Python-typisch, die typische `for` Schleife belegt diese Syntax aber schon. Stichwoerter sind Generator Expression (die wird hier verwendet) und List Comprehension. Es wird ein Generator erzeugt, der nacheinander alle 'INHALT' Gruppen aller Treffer gibt.

2. Nein, `set` bekommt _alle_ Werte aufeinmal in einer Sequenz und konsumiert sie dabei. Zur Vereinfachung kannst du dir die Generator Expression (also das Argument von `set`) als Liste vorstellen.

3. `sorted` ist eingebaut: http://docs.python.org/2/library/functions.html#sorted

4. So ungefaehr, die "uebergebene Gruppe" ist fuer `str.join` eben eine beliebige Sequenz.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Klärt mich bitte auf: was soll das <NULL> bedeuten? Wenn ich es weglasse oder durch ein <huhu> ersetze ändert sich nichts an der Ausgabe. :K
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@NoPy: 1. Ist ein Generatorausdruck. Also eine „list comprehension” in runden Klammern, wobei man die runden Klammern von einem Funktionsaufruf ”mitbenutzen” kann, solange dadurch keine Mehrdeutigkeiten entstehen. In dem Fall weigert der Compiler sich das ohne extra Klammern zu nehmen, das kann also nicht aus versehen passieren.

Das Ergebnis eines Generatorausdrucks ist „iterierbar”, das heisst man kann über die einzelnen Elemente zum Beispiel mit einer ``for``-Schleife iterieren. Der Begriff „iterierbar” kommt als Eigenschaft von Objekten in Python recht häufig vor. So ein Generator-Objekt garantiert noch ein bisschen mehr, nämlich das es selbst ein Iterator ist. Man kann mit `next()` das jeweils nächste Element abfragen bis eventuell keine mehr da sind und eine `StopIteration`-Ausnahme ausgelöst wird.

Code: Alles auswählen

In [18]: a = (match.group('INHALT').strip() for match in re.finditer(regular_expression, string))

In [19]: a
Out[19]: <generator object <genexpr> at 0xa298644>

In [20]: next(a)
Out[20]: '(Das) 1'

In [21]: next(a)
Out[21]: '(ist) 2'

In [22]: next(a)
Out[22]: '(ein) 3'

In [23]: next(a)
Out[23]: '(Test) 4'

In [24]: next(a)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/home/bj/<ipython-input-24-3f6e2eea332d> in <module>()
----> 1 next(a)

StopIteration:
2. `set()` erzeugt ein `set()`-Objekt aus jedem beliebigen iterierbaren Objekt das „hash”- und vergleichbare (``==``) Objekte liefert. Listen, zum lesen geöffnete Textdateien, eben alles wo man mit einer ``for``-Schleife drüber gehen kann.

Code: Alles auswählen

In [26]: set(match.group('INHALT').strip() for match in re.finditer(regular_expression, string))
Out[26]: set(['(ist) 2', '(ein) 3', '(Das) 1', '(Test) 4'])
3. `sorted()` erstellt aus jedem beliebigen iterierbaren Objekt das vergleichbare (``<``/``>``) Objekte liefert, also unter anderem auch aus einem `set`-Objekt, eine sortierte Liste.

Code: Alles auswählen

In [27]: sorted(set(match.group('INHALT').strip() for match in re.finditer(regular_expression, string)))
Out[27]: ['(Das) 1', '(Test) 4', '(ein) 3', '(ist) 2']
4. Die `str.join()`-Methode nimmt als Argument jedes beliebige iterierbare Objekt das Zeichenketten liefert und erstellt daraus eine Gesamtzeichenkette mit der Zeichenkette auf der das Aufgerufen wurde als Trenner.

Code: Alles auswählen

In [28]: ', '.join(sorted(set(match.group('INHALT').strip() for match in re.finditer(regular_expression, string))))
Out[28]: '(Das) 1, (Test) 4, (ein) 3, (ist) 2'
Das Iterator-Konzept ist in Python ziemlich zentral. Jede ``for``-Schleife nutzt es und es gibt viele Funktionen die etwas iterierbares als Argument nehmen und/oder als Rückgabewert liefern. Zum Beispiel nehmen fast alle Container-Datentypen ein iterierbares Objekt als Argument beim erzeugen um sich zu füllen. Und Containerdatentypen selbst sind auch iterierbar.

Das mit dem Initialisieren von Variablen am Anfang führt halt gerne mal zu Fehlern, insbesondere wenn man den gleichen Namen öfter verwendet. Was man ja eigentlich auch nicht unbedingt machen sollte. Aber dann denkt man halt man weiss was der Name am Anfang mal zugewiesen bekommen hat, in der Zwischenzeit wurde aber schon mal etwas anderes damit gemacht. Ausserdem vergisst man gerne mal initialisierte Variablen die in der fertigen Fassung gar nicht mehr gebraucht werden. Und es ist schwieriger Teile einer Funktion in eine neue auszulagern wenn nicht alles was zusammengehört auch „räumlich” zusammen steht.

Wie gesagt: Wenn man nicht mehr auseinander halten kann welche Namen als Argumente übergeben wurde, und welche lokal definiert wurden, dann ist die Funktion so lang das man nicht mehr eben schnell in die Signatur schauen kann und es gibt sehr viele Namen. Ein Anzeichen, dass man vielleicht etwas umschreiben sollte. Sehr viele Lauf-, Index-, und Hilfsvariablen die man in „alten” Sprachen brauchte, sind in modernen Programmiersprachen auch gar nicht mehr nötig. Meine Fassung hat neben den beiden Argumenten ja nur noch einen anderen lokalen Namen (`match`).
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

NoPy hat geschrieben: Zu mehr bin ich im Moment nicht bereit, denn dann versteh ich nicht mehr, was da ist.
Dann musst Du aber damit rechnen, dass Du immer wieder auf die schlechte Namensgebung angesprochen wirst und zudem ggf. auch viele Regulars hier mit der Zeit die Lust verlieren, sich in solchen Code einzulesen... ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

@ pillmuncher: Doch, wenn <NULL> Bestandteil Deiner Zeichenkette ist. Oder anders: Das Matching soll passen bei (...) ..., aber auch bei <NULL>
@ BlackJack: Wieder etwas klarer
@ Hyperion: Ich weiß, aber ich kann es nicht ändern. Wenn meine Tante Eier hätte, wäre sie mein Onkel...
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

NoPy hat geschrieben: @ Hyperion: Ich weiß, aber ich kann es nicht ändern. Wenn meine Tante Eier hätte, wäre sie mein Onkel...
Du willst es nicht - ein entscheidender Unterschied ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

Hyperion hat geschrieben:
NoPy hat geschrieben: @ Hyperion: Ich weiß, aber ich kann es nicht ändern. Wenn meine Tante Eier hätte, wäre sie mein Onkel...
Du willst es nicht - ein entscheidender Unterschied ;-)
Die Grenzen sind fließend
... denn dann versteh ich nicht mehr, was da ist.
Ich würde mich all Euren Konventionen beugen, wenn ich dann noch durchsehen würde. In dem Maß, wie ich mehr durchsehe, werde ich auch mit Euren Konventionen zurechtkommen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

NoPy hat geschrieben:Ich würde mich all Euren Konventionen beugen, wenn ich dann noch durchsehen würde. In dem Maß, wie ich mehr durchsehe, werde ich auch mit Euren Konventionen zurechtkommen.
Nun stell dich mal so an ;-) Du tust ja gerade so, als wärst du der einzige mit mehreren Programmiersprachen oder sich widersprechenden Codestyle-Vorgaben.
Das Leben ist wie ein Tennisball.
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

EyDu hat geschrieben:
NoPy hat geschrieben:Ich würde mich all Euren Konventionen beugen, wenn ich dann noch durchsehen würde. In dem Maß, wie ich mehr durchsehe, werde ich auch mit Euren Konventionen zurechtkommen.
Nun stell dich mal so an ;-) Du tust ja gerade so, als wärst du der einzige mit mehreren Programmiersprachen oder sich widersprechenden Codestyle-Vorgaben.
Das gewiss nicht, aber anderen fällt es offensichtlich leichter :)
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

BlackJack hat geschrieben:@NoPy: 1. Ist ein Generatorausdruck. Also eine „list comprehension” in runden Klammern [...]
Schön erklärt, aber deine „Anführungszeichen“ sind falsch. Die sehen im Deutschen wie 99–66 aus. SCNR ;)
BlackJack

@Darii: Aber „list comprehension” ist doch gar nicht deutsch. :-P
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

[klugscheißer]Dann hieße es aber “list comprehension”.[/klugscheißer]
Benutzeravatar
NoPy
User
Beiträge: 158
Registriert: Samstag 28. Dezember 2013, 12:39

BlackJack hat geschrieben:@Darii: Aber „list comprehension” ist doch gar nicht deutsch. :-P
Sicher? Und ich dachte, das ist nur aus Faulheit kleingeschrieben, comprehension ein Eigenname und List wie die Tücke, also so etwas, wie „Trick Siebzehn" Nun verstehe ich doch wieder Bahnhof ...
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Da es ein Fachbegriff ist, ist es aber ziemlich egal, dass es auch ein englisches Fragment ist 8)
Ich bezweifle dass jemand der dem englischen maechtig ist, aber dem die noetige mathematische Vorbildung (Mengen) oder die passenden Programmiersprachen (Haskell, Python, ...) fehlen, damit etwas anfangen kann.
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

BlackJack hat geschrieben:@Darii: Aber „list comprehension” ist doch gar nicht deutsch. :-P
Englische “quotation marks” sind aber immer noch oben (66–99). :p
Antworten