PEP 616 – Methoden zum Entfernen von Präfixen und Suffixen in Zeichenketten
- Autor:
- Dennis Sweeney <sweeney.dennis650 at gmail.com>
- Sponsor:
- Eric V. Smith <eric at trueblade.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 19. Mär 2020
- Python-Version:
- 3.9
- Post-History:
- 20. Mär 2020
Zusammenfassung
Dies ist ein Vorschlag zur Aufnahme zweier neuer Methoden, removeprefix() und removesuffix(), in die APIs der verschiedenen Zeichenkettenobjekte von Python. Diese Methoden würden ein Präfix bzw. Suffix aus einer Zeichenkette entfernen, falls vorhanden, und würden zu Unicode str-Objekten, binären bytes- und bytearray-Objekten sowie collections.UserString hinzugefügt werden.
Begründung
Es gab wiederholte Probleme auf Python-Ideas [2] [3], Python-Dev [4] [5] [6] [7], dem Bug Tracker und StackOverflow [8], die sich auf Benutzerverwirrung bezüglich der bestehenden str.lstrip- und str.rstrip-Methoden beziehen. Diese Benutzer erwarten typischerweise das Verhalten von removeprefix und removesuffix, sind aber überrascht, dass der Parameter für lstrip als eine Menge von Zeichen interpretiert wird, nicht als eine Teilzeichenkette. Dieses wiederholte Problem ist ein Beweis dafür, dass diese Methoden nützlich sind. Die neuen Methoden ermöglichen eine klarere Umleitung von Benutzern zum gewünschten Verhalten.
Als weiterer Beweis für die Nützlichkeit dieser Methoden berichteten mehrere Benutzer auf Python-Ideas [2] häufig, dass sie ähnliche Funktionen in ihrem Code zur Produktivitätssteigerung einfügen. Die Implementierung enthielt oft subtile Fehler bei der Behandlung von leeren Zeichenketten, so dass eine gut getestete integrierte Methode nützlich wäre.
Die bestehenden Lösungen zur Erzielung des gewünschten Verhaltens bestehen darin, entweder die Methoden wie in der Spezifikation unten zu implementieren oder reguläre Ausdrücke wie im Ausdruck re.sub('^' + re.escape(prefix), '', s) zu verwenden, was weniger auffindbar ist, einen Modulimport erfordert und zu weniger lesbarem Code führt.
Spezifikation
Die eingebaute Klasse str erhält zwei neue Methoden, die sich wie folgt verhalten, wenn type(self) is type(prefix) is type(suffix) is str
def removeprefix(self: str, prefix: str, /) -> str:
if self.startswith(prefix):
return self[len(prefix):]
else:
return self[:]
def removesuffix(self: str, suffix: str, /) -> str:
# suffix='' should not call self[:-0].
if suffix and self.endswith(suffix):
return self[:-len(suffix)]
else:
return self[:]
Wenn die Argumente Instanzen von str-Unterklassen sind, verhalten sich die Methoden so, als ob diese Argumente zuerst in Basis-str-Objekte umgewandelt worden wären, und der Rückgabewert ist immer eine Basis-str.
Methoden mit entsprechender Semantik werden den integrierten bytes- und bytearray-Objekten hinzugefügt. Wenn b entweder ein bytes- oder ein bytearray-Objekt ist, akzeptieren b.removeprefix() und b.removesuffix() jedes bytes-ähnliche Objekt als Argument. Die beiden Methoden werden auch zu collections.UserString mit ähnlichem Verhalten hinzugefügt.
Motivierende Beispiele aus der Python-Standardbibliothek
Die folgenden Beispiele zeigen, wie die vorgeschlagenen Methoden Code zu einem oder mehreren der folgenden machen können:
- Weniger fragil
Der Code hängt nicht davon ab, dass der Benutzer die Länge eines Literals zählt.
- Leistungsfähiger
Der Code erfordert keinen Aufruf der Python-eigenen Funktion
lenund auch nicht der teureren Methodestr.replace(). - Beschreibender
Die Methoden bieten eine höherwertige API für bessere Lesbarkeit des Codes im Gegensatz zur traditionellen Methode des String-Slicings.
find_recursionlimit.py
- Aktuell
if test_func_name.startswith("test_"): print(test_func_name[5:]) else: print(test_func_name)
- Verbessert
print(test_func_name.removeprefix("test_"))
deccheck.py
Dies ist ein interessanter Fall, da der Autor die Methode str.replace in einer Situation verwendet hat, in der nur ein Präfix entfernt werden sollte.
- Aktuell
if funcname.startswith("context."): self.funcname = funcname.replace("context.", "") self.contextfunc = True else: self.funcname = funcname self.contextfunc = False
- Verbessert
if funcname.startswith("context."): self.funcname = funcname.removeprefix("context.") self.contextfunc = True else: self.funcname = funcname self.contextfunc = False
- Argumentativ weiter verbessert
self.contextfunc = funcname.startswith("context.") self.funcname = funcname.removeprefix("context.")
test_i18n.py
- Aktuell
creationDate = header['POT-Creation-Date'] # peel off the escaped newline at the end of string if creationDate.endswith('\\n'): creationDate = creationDate[:-len('\\n')]
- Verbessert
creationDate = header['POT-Creation-Date'].removesuffix('\\n')
Es gab viele weitere solche Beispiele in der Standardbibliothek.
Abgelehnte Ideen
Erweiterung der lstrip- und rstrip-APIs
Da lstrip eine Zeichenkette als Argument nimmt, könnte es als Akzeptanz eines Iterables von Zeichenketten der Länge 1 betrachtet werden. Die API könnte daher verallgemeinert werden, um jedes Iterable von Zeichenketten zu akzeptieren, die nacheinander als Präfixe entfernt würden. Obwohl dieses Verhalten konsistent wäre, wäre es für Benutzer nicht offensichtlich, 'foobar'.lstrip(('foo',)) für den gängigen Anwendungsfall eines einzelnen Präfixes aufrufen zu müssen.
Mehrere Kopien eines Präfixes entfernen
Dies ist das Verhalten, das mit der oben genannten Erweiterung der lstrip/rstrip-API konsistent wäre – wiederholtes Anwenden der Funktion, bis das Argument unverändert ist. Dieses Verhalten kann durch Folgendes aus dem vorgeschlagenen Verhalten erreicht werden.
>>> s = 'Foo' * 100 + 'Bar'
>>> prefix = 'Foo'
>>> while s.startswith(prefix): s = s.removeprefix(prefix)
>>> s
'Bar'
Auslösen einer Ausnahme, wenn nicht gefunden
Es gab den Vorschlag, dass s.removeprefix(pre) eine Ausnahme auslösen sollte, wenn not s.startswith(pre). Dies stimmt jedoch nicht mit dem Verhalten und Gefühl anderer Zeichenkettenmethoden überein. Es könnte ein Schlüsselwortargument required=False hinzugefügt werden, aber dies verstößt gegen das KISS-Prinzip.
Akzeptieren eines Tupels von Affixen
Es könnte praktisch sein, das obige Beispiel test_concurrent_futures.py als name.removesuffix(('Mixin', 'Tests', 'Test')) zu schreiben, daher gab es den Vorschlag, dass die neuen Methoden ein Tupel von Zeichenketten als Argument akzeptieren könnten, ähnlich der startswith() API. Innerhalb des Tupels würde nur der erste passende Affix entfernt werden. Dies wurde aus folgenden Gründen abgelehnt.
- Dieses Verhalten kann überraschend oder visuell verwirrend sein, insbesondere wenn ein Präfix leer ist oder eine Teilzeichenkette eines anderen Präfixes ist, wie in
'FooBar'.removeprefix(('', 'Foo')) == 'FooBar'oder'FooBar text'.removeprefix(('Foo', 'FooBar ')) == 'Bar text'. - Die API für
str.replace()akzeptiert nur ein einzelnes Paar von Ersetzungszeichenketten, hat aber die Zeit überdauert, indem sie der Versuchung widerstand, im Angesicht von mehrdeutigen Mehrfachersetzungen zu raten. - Es mag in Zukunft einen überzeugenden Anwendungsfall für ein solches Feature geben, aber eine Verallgemeinerung, bevor das grundlegende Feature im wirklichen Leben verwendet wird, wäre leicht permanent falsch zu machen.
Alternative Methodennamen
Mehrere alternative Methodennamen wurden vorgeschlagen. Einige sind unten aufgeführt, zusammen mit Kommentaren, warum sie zugunsten von removeprefix abgelehnt werden sollten (die gleichen Argumente gelten für removesuffix).
ltrim,trimprefix, etc.„Trim“ macht in anderen Sprachen (z. B. JavaScript, Java, Go, PHP) das, was die
strip-Methoden in Python tun.lstrip(string=...)Dies würde die Hinzufügung einer neuen Methode vermeiden, aber für ein anderes Verhalten ist es besser, zwei verschiedene Methoden zu haben als eine Methode mit einem Schlüsselwortargument, das das Verhalten auswählt.
remove_prefix:Alle anderen Methoden der String-API, z. B.
str.startswith(), verwendenkleinschreibunganstelle vonklein_schreibung_mit_unterstrichen.removeleft,leftremoveoderlremoveDie Deutlichkeit von „Präfix“ wird bevorzugt.
cutprefix,deleteprefix,withoutprefix,dropprefix, etc.Viele davon wären akzeptabel gewesen, aber „remove“ ist unmissverständlich und passt dazu, wie man das Verhalten „remove the prefix“ auf Englisch beschreiben würde.
stripprefix:Benutzer könnten davon profitieren, sich daran zu erinnern, dass „strip“ bedeutet, mit Zeichensätzen zu arbeiten, während andere Methoden mit Teilzeichenketten arbeiten. Daher sollte die Wiederverwendung von „strip“ hier vermieden werden.
Wie man das lehrt
Unter den Verwendungen der String-Methoden partition(), startswith() und split() oder der eingebauten Funktionen enumerate() oder zip() ist ein gemeinsames Thema, dass wenn ein Anfänger manuell eine Zeichenkette indiziert oder slicet, er überlegen sollte, ob es eine höherwertige Methode gibt, die besser kommuniziert, *was* der Code tun soll, und nicht nur *wie* der Code es tun soll. Die vorgeschlagenen Methoden removeprefix() und removesuffix() erweitern die höherwertige String-"Werkzeugkiste" und ermöglichen weiterhin diese Art von Skepsis gegenüber manuellem Slicing.
Die Hauptgelegenheit für Benutzerverwirrung wird die Vermischung von lstrip/rstrip mit removeprefix/removesuffix sein. Es kann daher hilfreich sein, die folgenden Unterschiede zwischen den Methoden hervorzuheben (wie es die Dokumentation tun wird).
(l/r)strip:- Das Argument wird als Zeichensatz interpretiert.
- Die Zeichen werden wiederholt vom entsprechenden Ende der Zeichenkette entfernt.
remove(prefix/suffix):- Das Argument wird als ununterbrochene Teilzeichenkette interpretiert.
- Es wird höchstens eine Kopie des Präfixes/Suffixes entfernt.
Referenzimplementierung
Siehe den Pull Request auf GitHub [1].
Geschichte der wichtigsten Überarbeitungen
- Version 3: Entfernen des Tupel-Verhaltens.
- Version 2: Geänderter Name in
removeprefix/removesuffix; Unterstützung für Tupel als Argumente hinzugefügt - Version 1: Erster Entwurf mit
cutprefix/cutsuffix
Referenzen
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0616.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT