Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 502 – String Interpolation - Erweiterte Diskussion

Autor:
Mike G. Miller
Status:
Abgelehnt
Typ:
Informational
Erstellt:
10. Aug. 2015
Python-Version:
3.6

Inhaltsverzeichnis

Zusammenfassung

PEP 498: Literal String Formatting, das "formatierte Strings" vorschlug, wurde am 9. September 2015 angenommen. Zusätzlicher Hintergrund und Begründungen während seiner Entwurfsphase sind unten detailliert.

Um diesen PEP zusammenzufassen, wurde ein String-Präfix eingeführt, das den String als Vorlage zur Darstellung markiert. Diese formatierten Strings können einen oder mehrere Ausdrücke enthalten, die auf der bestehenden Syntax von str.format() basieren. [10] [11] Der formatierte String wird zur Kompilierungszeit in einen konventionellen String-Formatierungsaufruf expandiert, wobei die gegebenen Ausdrücke aus seinem Text extrahiert und stattdessen als positionelle Argumente übergeben werden.

Zur Laufzeit werden die resultierenden Ausdrücke ausgewertet, um einen String gemäß den gegebenen Spezifikationen zu rendern.

>>> location = 'World'
>>> f'Hello, {location} !'      # new prefix: f''
'Hello, World !'                # interpolated result

Format-Strings können als rein syntaktischer Zucker betrachtet werden, um traditionelle Aufrufe von str.format() zu vereinfachen.

PEP Status

Dieser PEP wurde abgelehnt, da er einen meinungsbasierten Ton anstelle eines sachlichen verwendete. Dieser PEP wurde auch als nicht kritisch eingestuft, da PEP 498 bereits geschrieben war und der Ort sein sollte, an dem die Designentscheidungsdetails untergebracht werden.

Motivation

Obwohl Python über eine Fülle von String-Formatierungs- und Manipulationsfunktionen verfügt, ist ein Bereich, in dem es Defizite aufweist, das Fehlen einer praktischen String-Interpolationssyntax. Im Vergleich zu anderen dynamischen Skriptsprachen mit ähnlichen Anwendungsfällen ist der für die Erstellung ähnlicher Strings erforderliche Code erheblich höher, während er manchmal eine geringere Lesbarkeit aufgrund von Ausführlichkeit, dichter Syntax oder doppelten Bezeichnern bietet.

Diese Schwierigkeiten werden im ursprünglichen Post an python-ideas, der den Stein ins Rollen brachte (der zu PEP 498 wurde), ausführlich beschrieben. [1]

Darüber hinaus hat die Ersetzung der `print`-Anweisung durch die konsistentere `print`-Funktion von Python 3 (PEP 3105) eine zusätzliche geringfügige Belastung hinzugefügt, nämlich einen zusätzlichen Satz von Klammern, die eingetippt und gelesen werden müssen. In Kombination mit der Ausführlichkeit aktueller String-Formatierungslösungen gerät eine ansonsten einfache Sprache hierdurch in einen unglücklichen Nachteil gegenüber ihren Mitbewerbern.

echo "Hello, user: $user, id: $id, on host: $hostname"              # bash
say  "Hello, user: $user, id: $id, on host: $hostname";             # perl
puts "Hello, user: #{user}, id: #{id}, on host: #{hostname}\n"      # ruby
                                                                    # 80 ch -->|
# Python 3, str.format with named parameters
print('Hello, user: {user}, id: {id}, on host: {hostname}'.format(**locals()))

# Python 3, worst case
print('Hello, user: {user}, id: {id}, on host: {hostname}'.format(user=user,
                                                                  id=id,
                                                                  hostname=
                                                                    hostname))

In Python ist die Formatierung und Ausgabe eines Strings mit mehreren Variablen in einer einzigen Zeile Code von Standardbreite merklich schwieriger und ausführlicher, wobei die Einrückung das Problem noch verschärft.

Für Anwendungsfälle wie kleinere Projekte, Systemprogrammierung, Shell-Skript-Ersatz und sogar Einzeiler, bei denen die Komplexität der Nachrichtenformatierung noch nicht gekapselt ist, hat diese Ausführlichkeit wahrscheinlich dazu geführt, dass sich eine beträchtliche Anzahl von Entwicklern und Administratoren im Laufe der Jahre für andere Sprachen entschieden hat.

Begründung

Ziele

Die Designziele von Format-Strings sind wie folgt:

  1. Notwendigkeit der manuellen Übergabe von Variablen eliminieren.
  2. Wiederholung von Bezeichnern und redundante Klammern eliminieren.
  3. Umständliche Syntax, Satzzeichen und visuelles Rauschen reduzieren.
  4. Lesbarkeit verbessern und Fehlerinkongruenzen eliminieren, indem benannte Parameter gegenüber positionsalen Argumenten bevorzugt werden.
  5. Vermeidung der Notwendigkeit der Verwendung von locals() und globals(), stattdessen Analyse des gegebenen Strings auf benannte Parameter und deren automatische Übergabe. [2] [3]

Einschränkungen

Im Gegensatz zu anderen Sprachen, die Designelemente von Unix und seinen Shells übernehmen, und im Gegensatz zu Javascript, spezifizierte Python sowohl einfache ('') als auch doppelte ("") ASCII-Anführungszeichen zur Umschließung von Strings. Es ist nicht ratsam, eines davon jetzt für die Interpolation zu wählen und das andere für nicht interpolierte Strings zu belassen. Andere Zeichen, wie das „Backtick“ (oder Grave Accent `) sind ebenfalls historisch eingeschränkt als Abkürzung für repr().

Dies lässt nur wenige Optionen für das Design eines solchen Features übrig:

  • Ein Operator, wie bei der Printf-Style-String-Formatierung über %.
  • Eine Klasse, wie string.Template().
  • Eine Methode oder Funktion, wie str.format().
  • Neue Syntax, oder
  • Ein neues String-Präfix-Zeichen, wie die bekannten r'' oder u''.

Die ersten drei Optionen sind ausgereift. Jede hat spezifische Anwendungsfälle und Nachteile, leidet aber auch unter der bereits erwähnten Ausführlichkeit und dem visuellen Rauschen. Alle Optionen werden in den nächsten Abschnitten diskutiert.

Hintergrund

Formatierte Strings bauen auf mehreren bestehenden Techniken und Vorschlägen auf und auf dem, was wir kollektiv von ihnen gelernt haben. Im Einklang mit den Designzielen von Lesbarkeit und Fehlervermeidung verwenden die folgenden Beispiele daher benannte, nicht positionale Argumente.

Nehmen wir an, wir haben das folgende Wörterbuch und möchten seine Elemente als informativen String für Endbenutzer ausgeben:

>>> params = {'user': 'nobody', 'id': 9, 'hostname': 'darkstar'}

Printf-style Formatierung, über Operator

Diese ehrwürdige Technik hat weiterhin ihre Verwendung, z. B. bei Byte-basierten Protokollen, Einfachheit in einfachen Fällen und Vertrautheit für viele Programmierer.

>>> 'Hello, user: %(user)s, id: %(id)s, on host: %(hostname)s' % params
'Hello, user: nobody, id: 9, on host: darkstar'

In dieser Form ist die Technik unter Berücksichtigung der vorausgesetzten Wörterbucherstellung ausführlich, ein wenig unordentlich, aber relativ gut lesbar. Weitere Probleme sind, dass ein Operator neben dem ursprünglichen String nur ein Argument annehmen kann, was bedeutet, dass mehrere Parameter als Tupel oder Wörterbuch übergeben werden müssen. Außerdem ist es relativ einfach, einen Fehler bei der Anzahl der übergebenen Argumente, dem erwarteten Typ, einem fehlenden Schlüssel oder dem Vergessen des abschließenden Typs, z. B. (s oder d), zu machen.

string.Template Klasse

Die string.Template Klasse aus PEP 292 (Simpler String Substitutions) ist ein bewusst vereinfachter Entwurf, der die vertraute Shell-Interpolationssyntax mit sicherer Substitutionsfunktion verwendet, die ihre Hauptanwendungsfälle in Shell- und Internationalisierungswerkzeugen findet.

Template('Hello, user: $user, id: ${id}, on host: $hostname').substitute(params)

Obwohl ebenfalls ausführlich, ist der String selbst lesbar. Obwohl die Funktionalität begrenzt ist, erfüllt sie ihre Anforderungen gut. Sie ist für viele Fälle nicht leistungsfähig genug, was unerfahrene Benutzer vor Problemen schützt und Probleme mit mäßig vertrauenswürdigen Eingaben (i18n) von Drittanbietern vermeidet. Es erfordert leider genügend Code, um seine Verwendung für Ad-hoc-String-Interpolation abzuschrecken, es sei denn, er wird in einer Komfortbibliothek wie flufl.i18n gekapselt.

PEP 215 - String Interpolation

PEP 215 war ein früherer Vorschlag, mit dem dieser viel gemeinsam hat. Anscheinend war die Welt zu dieser Zeit noch nicht bereit dafür, aber angesichts der jüngsten Unterstützung in einer Reihe anderer Sprachen könnte seine Zeit gekommen sein.

Die große Anzahl von Dollarzeichen ($), die er enthielt, könnte ihn Perl, Pythons Erzfeind, ähneln lassen und wahrscheinlich zum mangelnden Akzeptanz des PEP beigetragen haben. Er wurde durch den folgenden Vorschlag abgelöst.

str.format() Methode

Die str.format() Syntax von PEP 3101 ist die neueste und modernste der bestehenden Optionen. Sie ist auch leistungsfähiger und in der Regel besser lesbar als die anderen. Sie vermeidet viele der Nachteile und Einschränkungen der vorherigen Techniken.

Aufgrund des notwendigen Funktionsaufrufs und der Parameterübergabe ist sie jedoch in verschiedenen Situationen mit String-Literalen von ausführlich bis sehr ausführlich.

>>> 'Hello, user: {user}, id: {id}, on host: {hostname}'.format(**params)
'Hello, user: nobody, id: 9, on host: darkstar'

# when using keyword args, var name shortening sometimes needed to fit :/
>>> 'Hello, user: {user}, id: {id}, on host: {host}'.format(user=user,
                                                            id=id,
                                                            host=hostname)
'Hello, user: nobody, id: 9, on host: darkstar'

Die Ausführlichkeit des methodenbasierten Ansatzes wird hier veranschaulicht.

PEP 498 – Literal String Formatting

PEP 498 definiert und diskutiert Format-Strings, wie auch im Abstrakt oben beschrieben.

Er führt auch, für die ersten Betrachter etwas kontrovers, die Idee ein, dass Format-Strings mit Unterstützung für beliebige Ausdrücke erweitert werden sollen. Dies wird im Abschnitt "Restricting Syntax" unter Rejected Ideas weiter diskutiert.

PEP 501 – Übersetzungsfertige String-Interpolation

Der ergänzende PEP 501 bringt die Internationalisierung als primäre Sorge ein, mit seinem Vorschlag des i-Präfixes, der string.Template-Syntax-Integration, die mit ES6 (Javascript) kompatibel ist, verzögertem Rendering und einem Objekt-Rückgabewert.

Implementierungen in anderen Sprachen

String-Interpolation wird nun von verschiedenen Programmiersprachen, die in mehreren Branchen eingesetzt werden, gut unterstützt und konvergiert zu einer Art Standard. Sie konzentriert sich auf die str.format()-ähnliche Syntax in geringfügigen Variationen, ergänzt durch beliebige Ausdrücke zur Erweiterung des Nutzens.

Im Abschnitt Motivation wurde gezeigt, wie praktische Interpolationssyntax in Bash, Perl und Ruby existiert. Schauen wir uns ihre Ausdrucksunterstützung an.

Bash

Bash unterstützt eine Reihe von beliebigen, sogar rekursiven Konstrukten innerhalb von Strings:

> echo "user: $USER, id: $((id + 6)) on host: $(echo is $(hostname))"
user: nobody, id: 15 on host: is darkstar
  • Explizite Interpolation innerhalb von doppelten Anführungszeichen.
  • Direkter Zugriff auf Umgebungsvariablen wird unterstützt.
  • Beliebige Ausdrücke werden unterstützt. [4]
  • Ausführung externer Prozesse und Erfassung von Ausgaben werden unterstützt. [5]
  • Rekursive Ausdrücke werden unterstützt.

Perl

Perl hat ebenfalls beliebige Ausdruckskonstrukte, vielleicht nicht so bekannt:

say "I have @{[$id + 6]} guanacos.";                # lists
say "I have ${\($id + 6)} guanacos.";               # scalars
say "Hello { @names.join(', ') } how are you?";     # Perl 6 version
  • Explizite Interpolation innerhalb von doppelten Anführungszeichen.
  • Beliebige Ausdrücke werden unterstützt. [6] [7]

Ruby

Ruby erlaubt beliebige Ausdrücke in seinen interpolierten Strings:

puts "One plus one is two: #{1 + 1}\n"
  • Explizite Interpolation innerhalb von doppelten Anführungszeichen.
  • Beliebige Ausdrücke werden unterstützt. [8] [9]
  • Möglichkeit, Trennzeichen mit % zu ändern.
  • Siehe den Abschnitt "Referenzimplementierung(en)" für eine Implementierung in Python.

Andere

Betrachten wir einige weniger ähnliche moderne Sprachen, die kürzlich String-Interpolation implementiert haben.

Scala

Scala-Interpolation wird über String-Präfixe gesteuert. Jedes Präfix hat ein anderes Ergebnis.

s"Hello, $name ${1 + 1}"                    # arbitrary
f"$name%s is $height%2.2f meters tall"      # printf-style
raw"a\nb"                                   # raw, like r''

Diese Präfixe können auch vom Benutzer implementiert werden, indem die Klasse StringContext von Scala erweitert wird.

  • Explizite Interpolation innerhalb von doppelten Anführungszeichen mit Literal-Präfix.
  • Benutzerdefinierte Präfixe werden unterstützt.
  • Beliebige Ausdrücke werden unterstützt.

ES6 (Javascript)

Designer von Template-Strings standen vor dem gleichen Problem wie Python, wo einfache und doppelte Anführungszeichen bereits vergeben waren. Im Gegensatz zu Python waren die „Backticks“ jedoch nicht vergeben. Trotz ihrer Probleme wurden sie als Teil des ECMAScript 2015 (ES6) Standards gewählt.

console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);

Benutzerdefinierte Präfixe werden auch unterstützt, indem eine Funktion mit demselben Namen wie das Tag implementiert wird.

function tag(strings, ...values) {
    console.log(strings.raw[0]);    // raw string is also available
    return "Bazinga!";
}
tag`Hello ${ a + b } world ${ a * b}`;
  • Explizite Interpolation innerhalb von Backticks.
  • Benutzerdefinierte Präfixe werden unterstützt.
  • Beliebige Ausdrücke werden unterstützt.

C#, Version 6

C# hat ebenfalls eine nützliche neue Interpolationsfunktion mit gewisser Möglichkeit, die Interpolation anzupassen über das IFormattable-Interface.

$"{person.Name, 20} is {person.Age:D3} year{(p.Age == 1 ? "" : "s")} old.";
  • Explizite Interpolation mit doppelten Anführungszeichen und dem $-Präfix.
  • Benutzerdefinierte Interpolationen sind verfügbar.
  • Beliebige Ausdrücke werden unterstützt.

Apples Swift

Beliebige Interpolation unter Swift ist auf allen Strings verfügbar.

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
  • Implizite Interpolation mit doppelten Anführungszeichen.
  • Beliebige Ausdrücke werden unterstützt.
  • Kann CR/LF nicht enthalten.

Zusätzliche Beispiele

Eine Reihe von zusätzlichen Beispielen für String-Interpolation kann auf Wikipedia gefunden werden.

Nachdem nun Hintergrund und Geschichte behandelt wurden, fahren wir mit einer Lösung fort.

Neue Syntax

Dies sollte eine Option der letzten Instanz sein, da jede neue Syntaxfunktion einen Preis in Bezug auf den Platz in einem Gehirn hat, das sie bewohnt. Es gibt jedoch eine weitere verbleibende Möglichkeit auf unserer Liste, die folgt.

Neues String-Präfix

Angesichts der Geschichte der String-Formatierung in Python und der Abwärtskompatibilität, der Implementierungen in anderen Sprachen, der Vermeidung neuer Syntax, es sei denn, dies ist notwendig, wird ein akzeptables Design durch Eliminierung statt durch einzigartige Einsicht erreicht. Daher wird das Markieren interpolierter String-Literale mit einem String-Präfix gewählt.

Wir wählen auch eine Ausdruckssyntax, die die stärkste der bestehenden Optionen, str.format(), wiederverwendet und darauf aufbaut, um weitere Duplizierung von Funktionalität zu vermeiden.

>>> location = 'World'
>>> f'Hello, {location} !'      # new prefix: f''
'Hello, World !'                # interpolated result

PEP 498 – Literal String Formatting, befasst sich mit den Mechanismen und der Implementierung dieses Designs.

Zusätzliche Themen

Sicherheit

In diesem Abschnitt beschreiben wir die Sicherheitssituation und die getroffenen Vorsichtsmaßnahmen zur Unterstützung von Format-Strings.

  1. Nur String-Literale wurden für Format-Strings berücksichtigt, nicht Variablen, die als Eingabe genommen oder weitergegeben werden, was externe Angriffe schwierig macht.

    str.format() und Alternativen behandeln bereits diesen Anwendungsfall.

  2. Weder locals() noch globals() sind notwendig und werden während der Transformation nicht verwendet, wodurch Informationslecks vermieden werden.
  3. Um Komplexität sowie RuntimeError(s) aufgrund der Rekursionstiefe zu eliminieren, wird rekursive Interpolation nicht unterstützt.

Fehlerhafter oder bösartiger Code könnte jedoch innerhalb von String-Literalen übersehen werden. Obwohl dies auf Code im Allgemeinen zutrifft, ist es bei Ausdrücken, die sich innerhalb von Strings befinden, etwas wahrscheinlicher, dass sie verschleiert werden.

Minderung durch Werkzeuge

Die Idee ist, dass Werkzeuge oder Linter wie pyflakes, pylint oder Pycharm Strings mit Ausdrücken überprüfen und sie entsprechend hervorheben können. Da dies eine gängige Aufgabe in Programmiersprachen von heute ist, müssen Multi-Sprachen-Werkzeuge diese Funktion nicht ausschließlich für Python implementieren, was die Implementierungszeit erheblich verkürzt.

Weiter in der Zukunft könnten Strings auch auf Konstrukte überprüft werden, die die Sicherheitsrichtlinie eines Projekts überschreiten.

Styleguide/Vorsichtsmaßnahmen

Da beliebige Ausdrücke alles bewirken können, was ein Python-Ausdruck leisten kann, wird dringend empfohlen, Konstrukte innerhalb von Format-Strings zu vermeiden, die Nebeneffekte verursachen könnten.

Weitere Richtlinien können geschrieben werden, sobald Nutzungsmuster und tatsächliche Probleme bekannt sind.

Referenzimplementierung(en)

Das say-Modul auf PyPI implementiert String-Interpolation, wie hier beschrieben, mit der geringen Belastung einer aufrufbaren Schnittstelle.

> pip install say

from say import say
nums = list(range(4))
say("Nums has {len(nums)} items: {nums}")

Eine Python-Implementierung der Ruby-Interpolation ist ebenfalls verfügbar. Sie verwendet das `codecs`-Modul für ihre Arbeit.

> pip install interpy

# coding: interpy
location = 'World'
print("Hello #{location}.")

Abwärtskompatibilität

Durch die Verwendung bestehender Syntax und die Vermeidung aktueller oder historischer Features wurden Format-Strings so konzipiert, dass sie bestehenden Code nicht beeinträchtigen und keine Probleme verursachen.

Zurückgestellte Ideen

Internationalisierung

Obwohl es sehr wünschenswert war, Internationalisierungsunterstützung zu integrieren (siehe PEP 501), weichen die feineren Details bei fast jedem Punkt ab, was eine gemeinsame Lösung unwahrscheinlich macht: [15]

  • Unterschiedliche Anwendungsfälle
  • Kompilierungs- vs. Laufzeitaufgaben
  • Anforderungen an die Interpolationssyntax
  • Zielgruppe
  • Sicherheitsrichtlinie

Abgelehnte Ideen

Syntaxbeschränkung auf nur str.format()

Die gängigen Argumente gegen die Unterstützung von beliebigen Ausdrücken waren:

  1. YAGNI, "You Aren't Gonna Need It."
  2. Das Feature ist nicht kongruent mit dem historischen Python-Konservatismus.
  3. Verschieben - kann in einer zukünftigen Version implementiert werden, wenn Bedarf nachgewiesen wird.

Die Unterstützung nur der str.format()-Syntax wurde jedoch nicht als ausreichende Lösung des Problems angesehen. Oft ist vor dem Drucken beispielsweise die einfache Länge oder Inkrementierung eines Objekts gewünscht.

Im Abschnitt Implementierungen in anderen Sprachen ist zu sehen, dass die Entwicklergemeinschaft im Allgemeinen zustimmt. String-Interpolation mit beliebigen Ausdrücken wird in modernen Sprachen aufgrund ihres Nutzens zu einem Industriestandard.

Zusätzliche/Benutzerdefinierte String-Präfixe

Wie im Abschnitt Implementierungen in anderen Sprachen zu sehen ist, verfügen viele moderne Sprachen über erweiterbare String-Präfixe mit einer gemeinsamen Schnittstelle. Dies könnte eine Möglichkeit sein, Code in gängigen Situationen zu verallgemeinern und zu reduzieren. Beispiele finden sich in ES6 (Javascript), Scala, Nim und C# (in geringerem Maße). Dies wurde vom BDFL abgelehnt. [14]

Automatisches Escaping von Eingabevariablen

Obwohl in einigen Fällen hilfreich, wurde angenommen, dass dies zu viel Unsicherheit darüber schafft, wann und wo String-Ausdrücke sicher verwendet werden können oder nicht. Das Konzept war auch schwer anderen zu vermitteln. [12]

Betrachten Sie String-Formatierungsvariablen immer als unescaped, es sei denn, der Entwickler hat sie explizit escaped.

Umgebungsvariablenzugriff und Befehlssubstitution

Für Systemprogrammierung und Shell-Skript-Ersatz wäre es nützlich, Umgebungsvariablen zu verarbeiten und Ausgaben von Befehlen direkt in einem Ausdrucksstring zu erfassen. Dies wurde als nicht wichtig genug abgelehnt und ähnelte zu sehr Bash/Perl, was zu schlechten Gewohnheiten ermutigen könnte. [13]

Danksagungen

  • Eric V. Smith für die Erstellung und Implementierung von PEP 498.
  • Alle auf der Mailingliste python-ideas dafür, die verschiedenen verrückten Ideen abzulehnen, die aufkamen, und dabei zu helfen, das endgültige Design im Fokus zu behalten.

Referenzen


Source: https://github.com/python/peps/blob/main/peps/pep-0502.rst

Last modified: 2025-02-01 08:55:40 GMT