Gibt es sowas wie: str.split(sep, num, fill=None)?

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
gotridofmyphone
User
Beiträge: 26
Registriert: Mittwoch 15. März 2017, 08:54

Montag 5. März 2018, 13:29

Gibt es eine kürzere Lösung als

Code: Alles auswählen

    if '#' in k:
        k, idv = k.split("#", 2)
    else:
        idv = None
... die zudem noch skalabel ist, aber ohne an Verständlichkeit einzubüßen? Es wäre schön, wenn es so etwas gäbe wie str.split(sep, num, fill=None), die den Wert fill verwendet, um ggf. eine Liste der Länge num aufzufüllen. Das real verfügbare str.split(sep, maxnum) liefert höchstens maxnum Elemente zurück, aber ggf. weniger, was die Funktion für list assignment unbrauchbar macht. Man benötigt also immer eine temporäre benannte Liste. Es dürfte nicht allzu viel Aufwand sein, drumherum eine Schleife zu wickeln, habe ja schon leidliche Erfahrung mit der Auftrennung von Dateipfaden gemacht, das bei Python auch relativ umständlich ist. Aber dennoch, habe icih vielleicht was übersehen?


getridofmyphone
__deets__
User
Beiträge: 3353
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 5. März 2018, 13:50

Zuerstmal ist dein code falsch - die Angabe des zweiten Parameters ist die Anzahl der gefundenen Trenner, nicht die Anzahl der erzeugten Segmente:

Code: Alles auswählen

>>> "a#b#c".split("#", 2)
['a', 'b', 'c']
>>> "a#b#c".split("#", 1)
['a', 'b#c']
In letzter Zeit hat Sirius3 hier die mir bis dato unbekannt partition-Methode erwaehnt, die liefert immer drei Werte zurueck:

Code: Alles auswählen

>>> "a#b#c".partition("#")
('a', '#', 'b#c')
>>> "a".partition("#")
('a', '', '')
Das geht ggf. etwas weiter in die Richtung die dir vorschwebt. Und last but not least: schreib dir eine Funktion die tut was du willst. Und benutz die.
gotridofmyphone
User
Beiträge: 26
Registriert: Mittwoch 15. März 2017, 08:54

Montag 5. März 2018, 14:29

Zuerstmal ist dein code falsch - die Angabe des zweiten Parameters ist die Anzahl der gefundenen Trenner, nicht die Anzahl der erzeugten Segmente:
Ah, danke. Den Fehler mache ich bestimmt noch einige Male, bevor ich mich an diesen Unterschied zu Perl gewöhnt habe.

Zum Tipp mit der eigenen Funktion: Schon klar, aber Funktionen, die der Sprache "innewohnende", mir vielleicht nur nicht bekannte Features implementieren, wären auch etwas unnötig, mindestens eine Fehlerquelle für sich. Aber wenn es dieses Feature nun mal nicht gibt, bleibt mir in dem Fall eben nichts anderes übrig. Danke.

partition() ist nicht das, was ich will. Es liefert alles vor dem Suchstring, den Suchstring selbst, und alles danach, egal ob und wie oft der Suchstring darin enthalten ist. Ein mögliches Einsatzszenario fällt mir übrigens nicht ein, schon gar nicht warum das wichtiger sein sollte als ein fill-Argument für str.split(), aber das ist wohl in meinem Horizont begründet. Wer ne Idee?
gotridofmyphone
User
Beiträge: 26
Registriert: Mittwoch 15. März 2017, 08:54

Montag 5. März 2018, 16:11

Hier mal einen Entwurf einer eigenen Funktion zwecks Codekritik:

Code: Alles auswählen

def ext_split (string, sep, num, fill=None):

    if num:
        num -= 1
        if not num:
            return (string,)
        head, sep, tail = string.partition(sep)
        return (head,) + (
            ext_split(tail, sep, num, fill)
                if sep
                else (fill,) * num
        )

    else:
        return tuple()
        

print( ext_split( 'a#b#c', '#', 2 ) ) # ('a', 'b#c')
print( ext_split( 'a#b#c', '#', 3 ) ) # ('a', 'b', 'c')
print( ext_split( 'a#b#c', '#', 4 ) ) # ('a', 'b', 'c', None)
print( ext_split( 'a#b#c', '#', 5 ) ) # ('a', 'b', 'c', None, None)
__deets__
User
Beiträge: 3353
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 5. März 2018, 16:58

Wuerde ich mit itertools machen:

Code: Alles auswählen

from itertools import cycle, islice, chain

def ext_split(s, sep, num, fill=None):
    return list(islice(chain(s.split(sep, num - 1), cycle([fill])), 0, num))
Das list kann man sich auch noch sparen, dann waere es lazy, je nach Anwendungszweck.
narpfel
User
Beiträge: 226
Registriert: Freitag 20. Oktober 2017, 16:10

Montag 5. März 2018, 17:23

Die Iterator-Lösung von __deets__ ist zwar auch schön, aber ich würde in diesem Fall eher `list.extend` benutzen, weil `str.split` selbst keinen Iterator zurückgibt und man damit keine Lazyness gewonnen hat:

Code: Alles auswählen

def ext_split(s, sep, num, fill=None):
    if num < 0:
        raise ValueError("`num` must be non-negative")
    parts = s.split(sep, num - 1)
    parts.extend([fill] * (num - len(parts)))
    # alternativ:
    # parts.extend(repeat(fill, num - len(parts)))
    return parts
@__deets__: Das `islice` mit dem `cycle([fill])` kann man einfacher durch `repeat` ausdrücken:

Code: Alles auswählen

def ext_split(s, sep, num, fill=None):
    parts = s.split(sep, num - 1)
    return chain(parts, repeat(fill, num - len(parts)))
@gotridofmyphone: Zu deinem Code: Ein Tupel ist in diesem Fall die falsche Datenstruktur. Tupel stehen semantisch für heterogene Daten, wobei jeder Index eine spezielle Bedeutung hat. Außerdem finde ich es sehr unschön, fehlende Werte durch `None` zu ersetzen. Wenn man mit den Daten weiterarbeitet, muss man jedes mal auf `None` prüfen, bevor man irgendetwas damit macht.

Außerdem ist deine Leerzeichensetzung um Klammern nicht konsistent und entspricht nicht PEP8.
__deets__
User
Beiträge: 3353
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 5. März 2018, 17:33

@narpfel: schoen mit repeat cycle und islice zu ersetzen!

Was die generelle Kritik angeht kommt es IMHO etwas auf den Kontext an. Wenn man mit None gleich sinnvolle defaults fuer sonst auftauchende Werte setzten kann, ist's ok. Aber allgemein hast du auch da recht - ich wuerde das so nicht machen.
funkheld
User
Beiträge: 224
Registriert: Sonntag 31. Oktober 2010, 09:26

Dienstag 6. März 2018, 11:51

Hab ihr schon einmal einen Geschwindigkeitsvergleich gemacht.
Es kommt ja nicht immer auf den schönen Code an.

Gruss
__deets__
User
Beiträge: 3353
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dienstag 6. März 2018, 11:58

@funkheld: Geschwindigkeit schaut man sich an, wenn man ein Performanceproblem hat. Nicht vorher. "premature optimization is the root of all evil".

Selbst wenn mit einem micro-benchmark rauskaeme das zB meine Loesung 10-mal langsamer ist als eine andere, aber sie dafuer deutlich "schoener" ist (ohne da jetzt zu sehr ins Detail gehen zu wollen was das heissen kann) - dann spielt das erst eine Rolle, wenn die Funktion auch oft und wiederholt aufgerufen wird. Denn verstaendlicher Code, den du auch noch in 3 Wochen oder Monaten nachvollziehen kannst, ist immer wertvoller als super-duper-cleverer-optimierter-code, in den du dich einarbeiten musst, und dessen Erweiterbarkeit beschraenkt ist.
gotridofmyphone
User
Beiträge: 26
Registriert: Mittwoch 15. März 2017, 08:54

Dienstag 6. März 2018, 12:56

Außerdem finde ich es sehr unschön, fehlende Werte durch `None` zu ersetzen.
Kommt darauf an, was man will. Manchmal muss man sich eben entscheiden, welche Exception im konkreten Problemkontext sprechender ist. "ValueError: not enough values to unpack" oder z.B. "TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'". Okay, man mag einwenden, dass man spätestens, wenn man sich die Frage nach der richtigeren Exception stellt, am besten gleich die richtigste aller Exceptions implementiert. Codequalität ist für mich aber auch, so zu programmieren, dass etwaige Fehlermeldunge, die man nicht erwartet, einen mit der Nase auf die Ursache stupsen.
narpfel
User
Beiträge: 226
Registriert: Freitag 20. Oktober 2017, 16:10

Dienstag 6. März 2018, 19:45

@gotridofmyphone: Es kommt nicht nur darauf an, welche Exception man haben möchte, sondern auch wann. Wenn du den `ValueError` beim Unpacking bekommst, dann weißt du genau, dass dein Programmierfehler in der Zeile liegt (oder beim Aufruf der Funktion mit falschen Parametern). Das Problem an `None` ist, dass es wie ein regulärer Wert durch’s halbe Programm gereicht kann und dann an einer komplett anderen Stelle zu einem `TypeError` führt. Aus dem Grund halte ich es generell für eine schlechte Idee, `None` übermäßig zu verwenden. Wenn man aber begründen kann, warum diese Art von Problem in einem konkreten Anwendungsfall nicht auftreten kann, kann es auch okay sein, wenn man `None` benutzt.

Zur Geschwindigkeitsmessung stimme ich __deets__ zu: Wenn man nicht messbar ein Performanceproblem hast, sollte man immer die am besten lesbare Version nehmen. Und wenn man ein Performanceproblem hat, dann ist die Wahrscheinlichkeit groß, dass eine Mikrooptimierung wie hier nicht genug Performance bringt und man sowieso einen anderen (besseren) Algorithmus nehmen muss.
Antworten