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

Python Enhancement Proposals

PEP 701 – Syntaktische Formalisierung von f-Strings

Autor:
Pablo Galindo <pablogsal at python.org>, Batuhan Taskaya <batuhan at python.org>, Lysandros Nikolaou <lisandrosnik at gmail.com>, Marta Gómez Macías <cyberwitch at google.com>
Discussions-To:
Discourse thread
Status:
Akzeptiert
Typ:
Standards Track
Erstellt:
15. Nov. 2022
Python-Version:
3.12
Post-History:
19. Dez. 2022
Resolution:
14. Mär. 2023

Inhaltsverzeichnis

Zusammenfassung

Dieses Dokument schlägt vor, einige der ursprünglich in PEP 498 formulierten Einschränkungen aufzuheben und eine formalisierte Grammatik für f-Strings bereitzustellen, die direkt in den Parser integriert werden kann. Die vorgeschlagene syntaktische Formalisierung von f-Strings wird einige kleine Nebeneffekte auf die Art und Weise haben, wie f-Strings geparst und interpretiert werden, was zu einer beträchtlichen Anzahl von Vorteilen für Endbenutzer und Bibliotheksentwickler führt und gleichzeitig die Wartungskosten für den Code zur Verarbeitung von f-Strings drastisch reduziert.

Motivation

Als f-Strings ursprünglich in PEP 498 eingeführt wurden, wurde die Spezifikation ohne eine formale Grammatik für f-Strings bereitgestellt. Zusätzlich enthält die Spezifikation mehrere Einschränkungen, die auferlegt wurden, damit die Verarbeitung von f-Strings in CPython implementiert werden konnte, ohne den bestehenden Lexer zu modifizieren. Diese Einschränkungen wurden bereits früher erkannt und frühere Versuche, sie in PEP 536 aufzuheben, wurden unternommen, aber keine dieser Arbeiten wurde jemals implementiert. Einige dieser Einschränkungen (ursprünglich gesammelt von PEP 536) sind:

  1. Es ist unmöglich, das Anführungszeichen, das den f-String begrenzt, innerhalb des Ausdrucksteils zu verwenden
    >>> f'Magic wand: { bag['wand'] }'
                                 ^
    SyntaxError: invalid syntax
    
  2. Ein zuvor erwogener Weg, dies zu umgehen, würde zu Escape-Sequenzen im ausgeführten Code führen und ist in f-Strings verboten
    >>> f'Magic wand { bag[\'wand\'] } string'
    SyntaxError: f-string expression portion cannot include a backslash
    
  3. Kommentare sind auch in mehrzeiligen f-Strings verboten
    >>> f'''A complex trick: {
    ... bag['bag']  # recursive bags!
    ... }'''
    SyntaxError: f-string expression part cannot include '#'
    
  4. Beliebige Verschachtelungen von Ausdrücken ohne Expansion von Escape-Sequenzen sind in vielen anderen Sprachen verfügbar, die eine String-Interpolationsmethode verwenden, die Ausdrücke anstelle von nur Variablennamen verwendet. Einige Beispiele:
    # Ruby
    "#{ "#{1+2}" }"
    
    # JavaScript
    `${`${1+2}`}`
    
    # Swift
    "\("\(1+2)")"
    
    # C#
    $"{$"{1+2}"}"
    

Diese Einschränkungen dienen aus Sicht des Sprachbenutzers keinem Zweck und können aufgehoben werden, indem f-String-Literalen eine reguläre Grammatik ohne Ausnahmen gegeben und diese mit dediziertem Parse-Code implementiert wird.

Das andere Problem bei f-Strings ist, dass die aktuelle Implementierung in CPython auf die Tokenisierung von f-Strings als STRING-Token und eine Nachbearbeitung dieser Token angewiesen ist. Dies hat folgende Probleme:

  1. Es erhöht die Wartungskosten für den CPython-Parser erheblich. Dies liegt daran, dass der Parsing-Code von Hand geschrieben werden muss, was historisch zu einer beträchtlichen Anzahl von Inkonsistenzen und Fehlern geführt hat. Das manuelle Schreiben und Warten von Parsing-Code in C wurde schon immer als fehleranfällig und gefährlich angesehen, da es eine Menge manueller Speicherverwaltung über die ursprünglichen Lexer-Puffer hinweg erfordert.
  2. Der f-String-Parsing-Code kann die neuen verbesserten Fehlermeldungsmechanismen, die der neue PEG-Parser, ursprünglich eingeführt in PEP 617, ermöglicht hat, nicht nutzen. Die Verbesserungen, die diese Fehlermeldungen brachten, wurden sehr begrüßt, aber leider können f-Strings nicht davon profitieren, da sie in einem separaten Teil der Parsing-Maschinerie verarbeitet werden. Dies ist besonders bedauerlich, da es mehrere syntaktische Merkmale von f-Strings gibt, die aufgrund der unterschiedlichen impliziten Tokenisierung im Ausdrucksteil (z. B. f"{y:=3}" ist kein Zuweisungsausdruck) verwirrend sein können.
  3. Andere Python-Implementierungen haben keine Möglichkeit zu wissen, ob sie f-Strings korrekt implementiert haben, da sie im Gegensatz zu anderen Sprachmerkmalen nicht Teil der offiziellen Python-Grammatik sind. Dies ist wichtig, da mehrere prominente alternative Implementierungen den PEG-Parser von CPython verwenden, wie z. B. PyPy, und/oder ihre Grammatiken auf der offiziellen PEG-Grammatik basieren. Die Tatsache, dass f-Strings einen separaten Parser verwenden, hindert diese alternativen Implementierungen daran, die offizielle Grammatik zu nutzen und von Verbesserungen der Fehlermeldungen zu profitieren, die sich aus der Grammatik ergeben.

Eine Version dieses Vorschlags wurde ursprünglich auf Python-Dev diskutiert und auf dem Python Language Summit 2022 vorgestellt, wo sie enthusiastisch aufgenommen wurde.

Begründung

Aufbauend auf dem neuen Python PEG Parser (PEP 617) schlägt dieser PEP vor, „f-Strings“ neu zu definieren, wobei die klare Trennung der Zeichenkettenkomponente und der Ausdruckskomponente (oder Ersetzung, {...}) besonders hervorgehoben wird. PEP 498 fasst den syntaktischen Teil von „f-Strings“ wie folgt zusammen:

Im Python-Quellcode ist ein f-String ein Literalstring, der mit 'f' präfixiert ist und Ausdrücke in geschweiften Klammern enthält. Die Ausdrücke werden durch ihre Werte ersetzt.

Jedoch enthielt PEP 498 auch eine formale Liste von Ausschlüssen, was innerhalb der Ausdruckskomponente enthalten sein kann und was nicht (hauptsächlich aufgrund der Einschränkungen des bestehenden Parsers). Durch die klare Festlegung der formalen Grammatik haben wir nun auch die Möglichkeit, die Ausdruckskomponente eines f-Strings als wirklich „jeden anwendbaren Python-Ausdruck“ (in diesem speziellen Kontext) zu definieren, ohne durch die Einschränkungen der Implementierungsdetails gebunden zu sein.

Die Formalisierung und die obige Prämisse haben auch einen erheblichen Vorteil für Python-Programmierer, da sie in der Lage ist, obskure Einschränkungen zu vereinfachen und zu eliminieren. Dies reduziert die mentale Belastung und die kognitive Komplexität von f-String-Literalen (sowie der Python-Sprache im Allgemeinen).

  1. Die Ausdruckskomponente kann jede Zeichenkette enthalten, die ein normaler Python-Ausdruck enthalten kann. Dies eröffnet die Möglichkeit, Zeichenketten (formatiert oder nicht) innerhalb der Ausdruckskomponente eines f-Strings mit demselben Anführungszeichen (und derselben Länge) zu verschachteln.
    >>> f"These are the things: {", ".join(things)}"
    
    >>> f"{source.removesuffix(".py")}.c: $(srcdir)/{source}"
    
    >>> f"{f"{f"infinite"}"}" + " " + f"{f"nesting!!!"}"
    

    Dieses „Feature“ ist nicht allgemein als wünschenswert anerkannt, und einige Benutzer finden es unleserlich. Eine Diskussion über die unterschiedlichen Ansichten hierzu finden Sie im Abschnitt Überlegungen zur Wiederverwendung von Anführungszeichen.

  2. Ein weiteres Problem, das für die meisten intuitiv erschien, ist das Fehlen der Unterstützung für Backslashes innerhalb der Ausdruckskomponente eines f-Strings. Ein Beispiel, das immer wieder vorkommt, ist das Einfügen eines Zeilenumbruchs zum Verketten von Containern. Zum Beispiel:
    >>> a = ["hello", "world"]
    >>> f"{'\n'.join(a)}"
    File "<stdin>", line 1
        f"{'\n'.join(a)}"
                        ^
    SyntaxError: f-string expression part cannot include a backslash
    

    Ein gängiger Workaround dafür war entweder die Zuweisung des Zeilenumbruchs zu einer Zwischenvariablen oder die Vorab-Erstellung der gesamten Zeichenkette, bevor der f-String erstellt wurde.

    >>> a = ["hello", "world"]
    >>> joined = '\n'.join(a)
    >>> f"{joined}"
    'hello\nworld'
    

    Es erscheint nur natürlich, Backslashes im Ausdrucksteil zuzulassen, da der neue PEG-Parser dies nun problemlos unterstützen kann.

    >>> a = ["hello", "world"]
    >>> f"{'\n'.join(a)}"
    'hello\nworld'
    
  3. Vor den in diesem Dokument vorgeschlagenen Änderungen gab es keine explizite Grenze für die Verschachtelung von f-Strings, aber die Tatsache, dass Anführungszeichen innerhalb der Ausdruckskomponente von f-Strings nicht wiederverwendet werden konnten, machte es unmöglich, f-Strings beliebig zu verschachteln. Tatsächlich ist dies der am tiefsten verschachtelte f-String, der geschrieben werden kann:
    >>> f"""{f'''{f'{f"{1+1}"}'}'''}"""
    '2'
    

    Da dieser PEP die Platzierung von **beliebigen** gültigen Python-Ausdrücken innerhalb der Ausdruckskomponente von f-Strings erlaubt, ist es nun möglich, Anführungszeichen wiederzuverwenden und somit f-Strings beliebig zu verschachteln.

    >>> f"{f"{f"{f"{f"{f"{1+1}"}"}"}"}"}"
    '2'
    

    Obwohl dies nur eine Folge der Zulassung beliebiger Ausdrücke ist, glauben die Autoren dieses PEPs nicht, dass dies ein grundlegender Vorteil ist, und wir haben beschlossen, dass die Sprachspezifikation nicht explizit vorschreibt, dass diese Verschachtelung beliebig sein kann. Dies liegt daran, dass die Zulassung beliebig tiefer Verschachtelung eine Menge zusätzlicher Komplexität für die Lexer-Implementierung mit sich bringt (insbesondere da Lexer/Parser-Pipelines „entokenisieren“ müssen, um die „f-String-Debugging-Ausdrücke“ zu unterstützen, und dies ist besonders anstrengend, wenn beliebige Verschachtelungen zulässig sind). Implementierungen sind daher frei, eine Grenze für die Verschachtelungstiefe festzulegen, wenn sie dies benötigen. Beachten Sie, dass dies keine ungewöhnliche Situation ist, da die CPython-Implementierung bereits mehrere Grenzen überall auferlegt, einschließlich einer Grenze für die Verschachtelungstiefe von Klammern und eckigen Klammern, einer Grenze für die Verschachtelung von Blöcken, einer Grenze für die Anzahl der Zweige in if-Anweisungen, einer Grenze für die Anzahl der Ausdrücke in Stern-Entpackung usw.

Spezifikation

Die formale vorgeschlagene PEG-Grammatikspezifikation für f-Strings lautet (siehe PEP 617 für Details zur Syntax):

fstring
    | FSTRING_START fstring_middle* FSTRING_END
fstring_middle
    | fstring_replacement_field
    | FSTRING_MIDDLE
fstring_replacement_field
    | '{' (yield_expr | star_expressions) "="? [ "!" NAME ] [ ':' fstring_format_spec* ] '}'
fstring_format_spec:
    | FSTRING_MIDDLE
    | fstring_replacement_field

Die neuen Token (FSTRING_START, FSTRING_MIDDLE, FSTRING_END) werden später in diesem Dokument definiert.

Dieses PEP überlässt es der Implementierung, welche Ebene der f-String-Verschachtelung zulässig ist (f-Strings innerhalb der Ausdrucksteile anderer f-Strings), legt aber **eine untere Grenze von 5 Verschachtelungsebenen** fest. Dies soll sicherstellen, dass Benutzer eine vernünftige Erwartung haben können, f-Strings mit „angemessener“ Tiefe verschachteln zu können. Dieses PEP impliziert, dass die Begrenzung der Verschachtelung **nicht Teil der Sprachspezifikation** ist, aber auch die Sprachspezifikation **nicht beliebiges Verschachteln vorschreibt**.

Ebenso überlässt dieses PEP der Implementierung die Ebene der Ausdrucksverschachtelung in Format-Spezifikatoren, legt aber **eine untere Grenze von 2 Verschachtelungsebenen** fest. Das bedeutet, dass das Folgende immer gültig sein sollte:

f"{'':*^{1:{1}}}"

aber das Folgende kann je nach Implementierung gültig sein oder nicht:

f"{'':*^{1:{1:{1}}}}"

Die neue Grammatik wird den Abstract Syntax Tree (AST) der aktuellen Implementierung beibehalten. Das bedeutet, dass dieses PEP keine semantischen Änderungen an bestehendem Code einführt, der f-Strings verwendet.

Handhabung von f-String-Debug-Ausdrücken

Seit Python 3.8 können f-Strings zur Fehlerbehebung von Ausdrücken verwendet werden, indem der Operator = verwendet wird. Zum Beispiel:

>>> a = 1
>>> f"{1+1=}"
'1+1=2'

Diese Semantik wurde nicht formell in einem PEP eingeführt und wurde im aktuellen String-Parser als Sonderfall in bpo-36817 implementiert und im Abschnitt lexikalische Analyse von f-Strings dokumentiert.

Dieses Feature ist von den in diesem PEP vorgeschlagenen Änderungen nicht betroffen, aber es ist wichtig zu spezifizieren, dass die formale Handhabung dieses Features erfordert, dass der Lexer den Ausdrucksteil des f-Strings „entokenisieren“ kann. Dies ist kein Problem für den aktuellen String-Parser, da er direkt auf den String-Token-Inhalten arbeiten kann. Die Einbeziehung dieses Features in eine gegebene Parser-Implementierung erfordert jedoch, dass der Lexer die rohen String-Inhalte des Ausdrucksteils des f-Strings verfolgt und sie dem Parser zur Verfügung stellt, wenn der Parse-Baum für f-String-Knoten erstellt wird. Eine reine „Entokenisierung“ reicht nicht aus, da, wie derzeit spezifiziert, f-String-Debug-Ausdrücke Leerzeichen im Ausdruck beibehalten, einschließlich Leerzeichen nach den Zeichen { und =. Dies bedeutet, dass die rohen String-Inhalte des Ausdrucksteils des f-Strings intakt gehalten werden müssen und nicht nur die zugehörigen Token.

Wie Parser/Lexer-Implementierungen mit diesem Problem umgehen, bleibt natürlich der Implementierung überlassen.

Neue Token

Drei neue Token werden eingeführt: FSTRING_START, FSTRING_MIDDLE und FSTRING_END. Verschiedene Lexer können unterschiedliche Implementierungen haben, die effizienter sein können als die hier vorgeschlagenen, gegebenenfalls im Kontext der jeweiligen Implementierung. Die folgenden Definitionen werden jedoch als Teil der öffentlichen APIs von CPython (wie z. B. des Moduls tokenize) verwendet und auch als Referenz bereitgestellt, damit der Leser ein besseres Verständnis der vorgeschlagenen Grammatikänderungen und der Verwendung der Token erhält:

  • FSTRING_START: Dieses Token umfasst das f-String-Präfix (f/F/fr) und das öffnende Anführungszeichen (die Anführungszeichen).
  • FSTRING_MIDDLE: Dieses Token umfasst einen Teil des Textes innerhalb des Strings, der nicht Teil des Ausdrucksteils ist und keine öffnende oder schließende Klammer ist. Dies kann den Text zwischen dem öffnenden Anführungszeichen und der ersten Ausdrucksklammer ({), den Text zwischen zwei Ausdrucksklammern (} und {) und den Text zwischen der letzten Ausdrucksklammer (}) und dem schließenden Anführungszeichen umfassen.
  • FSTRING_END: Dieses Token umfasst das schließende Anführungszeichen.

Diese Token sind immer String-Teile und sind semantisch äquivalent zum STRING-Token mit den angegebenen Einschränkungen. Diese Token müssen vom Lexer beim Lexen von f-Strings erzeugt werden. Das bedeutet, dass **der Tokenizer keine einzelnen Token mehr für f-Strings erzeugen kann**. Wie der Lexer dieses Token ausgibt, ist **nicht spezifiziert**, da dies stark von jeder Implementierung abhängt (selbst die Python-Version des Lexers in der Standardbibliothek ist anders implementiert als die, die vom PEG-Parser verwendet wird).

Als Beispiel:

f'some words {a+b:.3f} more words {c+d=} final words'

wird tokenisiert als:

FSTRING_START - "f'"
FSTRING_MIDDLE - 'some words '
LBRACE - '{'
NAME - 'a'
PLUS - '+'
NAME - 'b'
OP - ':'
FSTRING_MIDDLE - '.3f'
RBRACE - '}'
FSTRING_MIDDLE - ' more words '
LBRACE - '{'
NAME - 'c'
PLUS - '+'
NAME - 'd'
OP - '='
RBRACE - '}'
FSTRING_MIDDLE - ' final words'
FSTRING_END - "'"

während f"""einige Wörter""" einfach als

FSTRING_START - 'f"""'
FSTRING_MIDDLE - 'some words'
FSTRING_END - '"""'

Änderungen am tokenize-Modul

Das tokenize-Modul wird so angepasst, dass es diese Token wie im vorherigen Abschnitt beschrieben ausgibt, wenn f-Strings geparst werden, sodass Tools von diesem neuen Tokenisierungs-Schema profitieren und sich nicht mit der Implementierung ihres eigenen f-String-Tokenizers und Parsers beschäftigen müssen.

Wie diese neuen Token erzeugt werden

Eine Möglichkeit, bestehende Lexer anzupassen, um diese Token auszugeben, ist die Integration eines Stacks von „Lexer-Modi“ oder die Verwendung eines Stacks verschiedener Lexer. Dies liegt daran, dass der Lexer von „regulärem Python-Lexing“ zu „f-String-Lexing“ wechseln muss, wenn er auf ein f-String-Start-Token stößt, und da f-Strings verschachtelt sein können, muss der Kontext erhalten bleiben, bis der f-String geschlossen wird. Außerdem muss der „Lexer-Modus“ innerhalb eines f-String-Ausdrucksteils als „Obermenge“ des regulären Python-Lexers fungieren (da er beim Erreichen des `}`-Terminators für den Ausdrucksteil wieder zum f-String-Lexing wechseln muss sowie f-String-Formatierung und Debug-Ausdrücke behandelt). Als Referenz ist hier ein Entwurf des Algorithmus zur Modifikation eines CPython-ähnlichen Tokenizers zur Ausgabe dieser neuen Token:

  1. Wenn der Lexer erkennt, dass ein f-String beginnt (durch Erkennung des Buchstabens ‚f/F‘ und eines der möglichen Anführungszeichen), fährt er fort, bis ein gültiges Anführungszeichen erkannt wird (eines von `"` , `"""` , `'` oder `'''`) und gibt ein FSTRING_START-Token mit dem erfassten Inhalt aus (das ‚f/F‘ und das startende Anführungszeichen). Legen Sie einen neuen Tokenizer-Modus auf den Tokenizer-Modus-Stack für „F-String-Tokenisierung“. Gehen Sie zu Schritt 2.
  2. Verbrauchen Sie weiter Token, bis eines der folgenden Elemente angetroffen wird:
    • Ein schließendes Anführungszeichen, das mit dem öffnenden Anführungszeichen übereinstimmt.
    • Wenn im „Format-Spezifizierungsmodus“ (siehe Schritt 3), eine öffnende Klammer (`{`), eine schließende Klammer (`}`) oder ein Zeilenumbruch-Token (`\n`).
    • Wenn nicht im „Format-Spezifizierungsmodus“ (siehe Schritt 3), eine öffnende Klammer (`{`) oder eine schließende Klammer (`}`) , die nicht unmittelbar von einer weiteren öffnenden/schließenden Klammer gefolgt wird.

    In allen Fällen, wenn der Zeichenpuffer nicht leer ist, geben Sie ein FSTRING_MIDDLE-Token mit dem bisher erfassten Inhalt aus, aber wandeln Sie doppelte öffnende/schließende Klammern in einzelne öffnende/schließende Klammern um. Fahren Sie nun wie folgt fort, abhängig vom angetroffenen Zeichen:

    • Wenn ein schließendes Anführungszeichen angetroffen wird, das mit dem öffnenden Anführungszeichen übereinstimmt, gehen Sie zu Schritt 4.
    • Wenn eine öffnende Klammer angetroffen wird (nicht unmittelbar von einer anderen öffnenden Klammer gefolgt), gehen Sie zu Schritt 3.
    • Wenn eine schließende Klammer angetroffen wird (nicht unmittelbar von einer anderen schließenden Klammer gefolgt), geben Sie ein Token für die schließende Klammer aus und gehen Sie zu Schritt 2.
  3. Legen Sie einen neuen Tokenizer-Modus auf den Tokenizer-Modus-Stack für „Reguläre Python-Tokenisierung innerhalb von f-Strings“ und fahren Sie mit der Tokenisierung damit fort. Dieser Modus tokenisiert wie die „Reguläre Python-Tokenisierung“, bis ein `:`- oder ein `}`-Zeichen angetroffen wird, das sich auf derselben Verschachtelungsebene befindet wie die öffnende Klammer, die beim Betreten des f-String-Teils gepusht wurde. Verwenden Sie diesen Modus, geben Sie Token aus, bis einer der Stopppunkte erreicht ist. Wenn dies geschieht, geben Sie das entsprechende Token für das angetroffene Stoppzeichen aus, popen Sie den aktuellen Tokenizer-Modus vom Tokenizer-Modus-Stack und gehen Sie zu Schritt 2. Wenn der Stopppunkt ein `:`-Zeichen ist, treten Sie in Schritt 2 im „Format-Spezifizierungs“-Modus ein.
  4. Geben Sie ein FSTRING_END-Token mit dem erfassten Inhalt aus und popen Sie den aktuellen Tokenizer-Modus (entsprechend „F-String-Tokenisierung“) und kehren Sie zum „Regulären Python-Modus“ zurück.

Natürlich ist es, wie bereits erwähnt, nicht möglich, eine präzise Spezifikation dafür zu geben, wie dies für einen beliebigen Tokenizer erfolgen sollte, da dies von der spezifischen Implementierung und der Art des zu ändernden Lexers abhängt.

Konsequenzen der neuen Grammatik

Alle in PEP erwähnten Einschränkungen werden aus f-String-Literalen aufgehoben, wie unten erklärt:

  • Ausdrucksteile dürfen nun Zeichenketten enthalten, die mit derselben Art von Anführungszeichen begrenzt sind, die zur Begrenzung des f-String-Literals verwendet werden.
  • Backslashes dürfen nun innerhalb von Ausdrücken erscheinen, genau wie überall sonst im Python-Code. Bei Zeichenketten, die innerhalb von f-String-Literalen verschachtelt sind, werden Escape-Sequenzen erweitert, wenn die innerste Zeichenkette ausgewertet wird.
  • Neue Zeilen sind nun innerhalb von Ausdrucksklammern erlaubt. Das bedeutet, dass folgende Konstrukte nun zulässig sind:
    >>> x = 1
    >>> f"___{
    ...     x
    ... }___"
    '___1___'
    
    >>> f"___{(
    ...     x
    ... )}___"
    '___1___'
    
  • Kommentare, die das Zeichen `#` verwenden, sind im Ausdrucksteil eines f-Strings erlaubt. Beachten Sie, dass Kommentare erfordern, dass die schließende Klammer (`}`) des Ausdrucksteils auf einer anderen Zeile als die Zeile des Kommentars vorhanden ist, andernfalls wird sie als Teil des Kommentars ignoriert.

Überlegungen zur Wiederverwendung von Anführungszeichen

Eine der Folgen der hier vorgeschlagenen Grammatik ist, dass, wie oben erwähnt, f-String-Ausdrücke nun Zeichenketten enthalten können, die mit derselben Art von Anführungszeichen begrenzt sind, die zur Begrenzung des äußeren f-String-Literals verwendet werden. Zum Beispiel:

>>> f" something { my_dict["key"] } something else "

Im Diskussions-Thread für dieses PEP wurden mehrere Bedenken bezüglich dieses Aspekts geäußert, und wir möchten sie hier sammeln, da sie bei der Annahme oder Ablehnung dieses PEPs berücksichtigt werden sollten.

Einige dieser Einwände umfassen:

  • Viele Leute finden die Wiederverwendung von Anführungszeichen innerhalb derselben Zeichenkette verwirrend und schwer zu lesen. Dies liegt daran, dass die Wiederverwendung von Anführungszeichen eine aktuelle Eigenschaft von Python verletzen würde: die Tatsache, dass Zeichenketten vollständig durch zwei aufeinanderfolgende Paare derselben Art von Anführungszeichen abgegrenzt sind, was an sich eine sehr einfache Regel ist. Einer der Gründe, warum die Wiederverwendung von Anführungszeichen für Menschen schwieriger zu analysieren sein kann und zu weniger lesbarem Code führt, ist, dass das Anführungszeichen für Start und Ende dasselbe ist (im Gegensatz zu anderen Trennzeichen).
  • Einige Benutzer haben Bedenken geäußert, dass die Wiederverwendung von Anführungszeichen einige Lexer und Syntax-Highlighting-Tools beeinträchtigen könnte, die auf einfache Mechanismen zur Erkennung von Zeichenketten und f-Strings angewiesen sind, wie z. B. reguläre Ausdrücke oder einfache Trennzeichen-Abgleichwerkzeuge. Die Einführung der Wiederverwendung von Anführungszeichen in f-Strings wird entweder die Funktionalität dieser Tools erschweren oder sie ganz unterbrechen (da z. B. reguläre Ausdrücke nicht beliebig verschachtelte Strukturen mit Trennzeichen parsen können). Der IDLE-Editor, der in der Standardbibliothek enthalten ist, ist ein Beispiel für ein Tool, das möglicherweise einige Anpassungen benötigt, um die Syntaxhervorhebung für f-Strings korrekt anzuwenden.

Hier sind einige der Argumente dafür:

  • Viele Sprachen, die ähnliche syntaktische Konstrukte (normalerweise „String-Interpolation“ genannt) zulassen, erlauben die Wiederverwendung von Anführungszeichen und beliebige Verschachtelungen. Zu diesen Sprachen gehören JavaScript, Ruby, C#, Bash, Swift und viele andere. Die Tatsache, dass viele Sprachen die Wiederverwendung von Anführungszeichen zulassen, kann ein überzeugendes Argument dafür sein, sie auch in Python zuzulassen. Dies liegt daran, dass die Sprache für Benutzer aus anderen Sprachen vertrauter wird.
  • Da viele andere beliebte Sprachen die Wiederverwendung von Anführungszeichen in String-Interpolationskonstrukten zulassen, bedeutet dies, dass Editoren, die die Syntaxhervorhebung für diese Sprachen unterstützen, bereits über die notwendigen Werkzeuge verfügen, um die Syntaxhervorhebung für f-Strings mit Wiederverwendung von Anführungszeichen in Python zu unterstützen. Das bedeutet, dass zwar die Dateien, die die Syntaxhervorhebung für Python verwalten, aktualisiert werden müssen, um dieses neue Feature zu unterstützen, es aber nicht als unmöglich oder sehr schwierig angesehen wird.
  • Ein Vorteil der Zulassung von Wiederverwendung von Anführungszeichen ist, dass sie sich sauber mit anderer Syntax komponiert. Manchmal wird dies als „referentielle Transparenz“ bezeichnet. Ein Beispiel dafür ist, dass, wenn wir f(x+1) haben und annehmen, dass a eine neu erstellte Variable ist, dies sich genauso verhalten sollte wie a = x+1; f(a). Und umgekehrt. Wenn wir also haben:
    def py2c(source):
        prefix = source.removesuffix(".py")
        return f"{prefix}.c"
    

    sollte erwartet werden, dass, wenn wir die Variable prefix durch ihre Definition ersetzen, das Ergebnis dasselbe ist:

    def py2c(source):
        return f"{source.removesuffix(".py")}.c"
    
  • Code-Generatoren (wie ast.unparse aus der Standardbibliothek) verlassen sich in ihrer aktuellen Form auf komplizierte Algorithmen, um sicherzustellen, dass Ausdrücke innerhalb eines f-Strings für den Kontext, in dem sie verwendet werden, geeignet sind. Diese nicht-trivialen Algorithmen bringen Herausforderungen mit sich, wie z. B. das Finden eines unbenutzten Anführungszeichens (durch Verfolgung der äußeren Anführungszeichen) und das Generieren von String-Darstellungen, die, wenn möglich, keine Backslashes enthalten. Die Zulassung von Wiederverwendung von Anführungszeichen und Backslashes würde die Code-Generatoren, die sich mit f-Strings befassen, erheblich vereinfachen, da die reguläre Python-Ausdruckslogik innerhalb und außerhalb von f-Strings ohne spezielle Behandlung verwendet werden kann.
  • Die Begrenzung der Wiederverwendung von Anführungszeichen würde die Komplexität der Implementierung der vorgeschlagenen Änderungen erheblich erhöhen. Dies liegt daran, dass der Parser den Kontext kennen muss, der einen Ausdrucksteil eines f-Strings mit einem bestimmten Anführungszeichen parst, um zu wissen, ob er einen Ausdruck ablehnen muss, der das Anführungszeichen wiederverwendet. Das Mitführen dieses Kontexts ist in Parsern, die beliebig zurückverfolgen können (wie der PEG-Parser), nicht trivial. Das Problem wird noch komplexer, wenn man bedenkt, dass f-Strings beliebig verschachtelt sein können und daher mehrere Anführungszeichentypen abgelehnt werden müssen.

    Um Feedback von der Community zu sammeln, wurde eine Umfrage initiiert, um ein Gefühl dafür zu bekommen, wie die Community zu diesem Aspekt des PEPs steht.

Abwärtskompatibilität

Dieses PEP führt keine rückwärts inkompatiblen syntaktischen oder semantischen Änderungen in der Python-Sprache ein. Das tokenize-Modul (ein quasi-öffentlicher Teil der Standardbibliothek) muss jedoch aktualisiert werden, um die neuen f-String-Token zu unterstützen (damit Toolautoren f-Strings korrekt tokenisieren können). Siehe Änderungen am tokenize-Modul für weitere Details darüber, wie die öffentliche API von tokenize betroffen sein wird.

Wie man das lehrt

Da das Konzept der f-Strings in der Python-Community bereits allgegenwärtig ist, gibt es keinen grundlegenden Bedarf für Benutzer, etwas Neues zu lernen. Da die formalisierte Grammatik jedoch einige neue Möglichkeiten zulässt, ist es wichtig, dass die formale Grammatik zur Dokumentation hinzugefügt und detailliert erklärt wird, wobei ausdrücklich erwähnt wird, welche Konstrukte seit diesem PEP möglich sind, um Verwirrung zu vermeiden.

Es ist auch vorteilhaft, den Benutzern einen einfachen Rahmen zu bieten, um zu verstehen, was in einen f-String-Ausdruck eingefügt werden kann. In diesem Fall denken die Autoren, dass diese Arbeit es noch einfacher macht, diesen Aspekt der Sprache zu erklären, da er sich zusammenfassen lässt als:

Sie können jeden gültigen Python-Ausdruck in einen f-String-Ausdruck einfügen.

Mit den Änderungen in diesem PEP ist es nicht mehr notwendig zu klären, dass Anführungszeichen auf unterschiedliche Anführungszeichen des umschließenden Strings begrenzt sind, da dies nun erlaubt ist: Da eine beliebige Python-Zeichenkette jede mögliche Auswahl von Anführungszeichen enthalten kann, so kann auch jeder f-String-Ausdruck. Zusätzlich ist es nicht mehr notwendig zu klären, dass bestimmte Dinge aufgrund von Implementierungseinschränkungen im Ausdrucksteil nicht erlaubt sind, wie z. B. Kommentare, Zeilenumbrüche oder Backslashes.

Der einzige „überraschende“ Unterschied ist, dass, da f-Strings die Angabe eines Formats zulassen, Ausdrücke, die ein `:`-Zeichen auf der obersten Ebene zulassen, immer noch in Klammern gesetzt werden müssen. Dies ist keine Neuheit dieser Arbeit, aber es ist wichtig zu betonen, dass diese Einschränkung weiterhin besteht. Dies ermöglicht eine einfachere Modifikation der Zusammenfassung:

Sie können jeden gültigen Python-Ausdruck in einen f-String-Ausdruck einfügen, und alles nach einem `:`-Zeichen auf der obersten Ebene wird als Format-Spezifikation identifiziert.

Referenzimplementierung

Eine Referenzimplementierung ist im Implementierungs-Fork zu finden.

Abgelehnte Ideen

  1. Obwohl wir die Lesbarkeitsargumente, die gegen die Zulassung von Wiederverwendung von Anführungszeichen in f-String-Ausdrücken vorgebracht wurden, für gültig und sehr wichtig halten, haben wir uns entschieden, die Wiederverwendung von Anführungszeichen in f-Strings nicht auf Parser-Ebene abzulehnen. Der Grund dafür ist, dass einer der Eckpfeiler dieses PEP die Reduzierung der Komplexität und Wartung des Parsens von f-Strings in CPython ist, und dies würde nicht nur gegen dieses Ziel arbeiten, sondern die Implementierung möglicherweise sogar noch komplexer machen als die aktuelle. Wir glauben, dass das Verbot der Wiederverwendung von Anführungszeichen in Linters und Code-Stil-Tools erfolgen sollte und nicht im Parser, genauso wie andere verwirrende oder schwer lesbare Konstrukte in der Sprache heute gehandhabt werden.
  2. Wir haben beschlossen, die Beschränkung, dass einige Ausdrucksteile `':'` und `'!'` in Klammern auf der obersten Ebene umschließen müssen, nicht aufzuheben, z. B.:
    >>> f'Useless use of lambdas: { lambda x: x*2 }'
    SyntaxError: unexpected EOF while parsing
    

    Der Grund dafür ist, dass dies eine erhebliche Komplexität ohne wirklichen Nutzen einführen würde. Dies liegt daran, dass das `:`-Zeichen normalerweise die f-String-Format-Spezifikation trennt. Diese Format-Spezifikation wird derzeit als Zeichenkette tokenisiert. Da der Tokenizer, was rechts von `:` steht, entweder als Zeichenkette oder als Strom von Token tokenisieren MUSS, wird dies dem Parser nicht erlauben, zwischen den verschiedenen Semantiken zu unterscheiden, da dies erfordern würde, dass der Tokenizer zurückverfolgt und einen anderen Satz von Token erzeugt (d. h. zuerst als Strom von Token versuchen und wenn dies fehlschlägt, als Zeichenkette für eine Format-Spezifikation versuchen).

    Da es keinen grundlegenden Vorteil darin gibt, Lambdas und ähnliche Ausdrücke auf der obersten Ebene zuzulassen, haben wir beschlossen, die Beschränkung beizubehalten, dass diese bei Bedarf in Klammern gesetzt werden müssen.

    >>> f'Useless use of lambdas: { (lambda x: x*2) }'
    
  3. Wir haben beschlossen (vorerst), die Verwendung von maskierten Klammern (`\{` und `\}`) zusätzlich zur `{{`- und `}}`-Syntax zu verbieten. Obwohl die Autoren des PEP glauben, dass die Zulassung von maskierten Klammern eine gute Idee ist, haben wir beschlossen, sie nicht in dieses PEP aufzunehmen, da sie für die hier vorgeschlagene Formalisierung von f-Strings nicht unbedingt erforderlich sind und unabhängig davon in einem regulären CPython-Issue hinzugefügt werden können.

Offene Fragen

Noch keine.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0701.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT