PEP 750 – Template Strings
- Autor:
- Jim Baker <jim.baker at python.org>, Guido van Rossum <guido at python.org>, Paul Everitt <pauleveritt at me.com>, Koudai Aono <koxudaxi at gmail.com>, Lysandros Nikolaou <lisandrosnik at gmail.com>, Dave Peck <davepeck at davepeck.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 08-Jul-2024
- Python-Version:
- 3.14
- Post-History:
- 09-Aug-2024, 17-Oct-2024, 21-Oct-2024, 18-Nov-2024
- Resolution:
- 10-Apr-2025
Inhaltsverzeichnis
- Zusammenfassung
- Beziehung zu anderen PEPs
- Motivation
- Spezifikation
- Template-String-Literale
- Der Typ
Template - Der Typ
Interpolation - Die
Template.valuesEigenschaft - Iterieren über
TemplateInhalte - Verarbeitung von Template-Strings
- Verkettung von Template-Strings
- Gleichheit von Template und Interpolation
- Keine Unterstützung für Ordering
- Unterstützung für den Debug-Spezifizierer (
=) - Rohe Template-Strings
- Auswertung von Interpolationsausdrücken
- Ausnahmen
- Keine
Template.__str__()Implementierung - Das Modul
string.templatelib
- Beispiele
- Abwärtskompatibilität
- Sicherheitsimplikationen
- Wie man das lehrt
- Warum ein anderer Templating-Ansatz?
- Häufige Muster bei der Verarbeitung von Templates
- Referenzimplementierung
- Abgelehnte Ideen
- Beliebige Präfixe für String-Literale
- Verzögerte Auswertung von Interpolationen
- Mache
TemplateundInterpolationzu Protokollen - Überschriebene
__eq__und__hash__fürTemplateundInterpolation - Ein zusätzlicher Typ
Decoded - Das endgültige Zuhause für
TemplateundInterpolation - Ermöglichen der vollständigen Rekonstruktion des ursprünglichen Template-Literals
- Deaktivieren der Template-Verkettung
- Beliebige Konvertierungswerte
- Entfernen von
conversionausInterpolation - Alternative Interpolationssymbole
- Alternative Layouts für
Template - Mechanismus zur Beschreibung der „Art“ eines Templates
- Binäre Template-Strings
- Danksagungen
- Urheberrecht
Zusammenfassung
Diese PEP führt Template-Strings für die benutzerdefinierte String-Verarbeitung ein.
Template-Strings sind eine Verallgemeinerung von f-Strings, die ein t anstelle des f-Präfixes verwenden. Anstatt zu str ausgewertet zu werden, werden t-Strings zu einem neuen Typ, Template, ausgewertet.
template: Template = t"Hello {name}"
Templates bieten Entwicklern Zugriff auf den String und seine interpolierten Werte, *bevor* sie kombiniert werden. Dies bringt native flexible String-Verarbeitung in die Python-Sprache und ermöglicht Sicherheitsprüfungen, Web-Templating, domänenspezifische Sprachen und mehr.
Beziehung zu anderen PEPs
Python führte f-Strings in Python 3.6 mit PEP 498 ein. Die Grammatik wurde dann in PEP 701 formalisiert, die auch einige Einschränkungen aufhob. Diese PEP basiert auf PEP 701.
Fast zur gleichen Zeit, als PEP 498 erschien, wurde PEP 501 verfasst, um „i-Strings“ bereitzustellen – also „Interpolation Template Strings“. Die PEP wurde wegen weiterer Erfahrungen mit f-Strings zurückgestellt. Die Arbeit an dieser PEP wurde im März 2023 von einem anderen Autor wieder aufgenommen und führte „t-Strings“ als Template-Literal-Strings ein, basierend auf PEP 701.
Die Autoren dieser PEP betrachten sie als eine Verallgemeinerung und Vereinfachung der aktualisierten Arbeit in PEP 501. (Diese PEP wurde ebenfalls kürzlich aktualisiert, um die neuen Ideen in dieser PEP widerzuspiegeln.)
Motivation
Python f-Strings sind einfach zu bedienen und sehr beliebt. Im Laufe der Zeit haben Entwickler jedoch Einschränkungen festgestellt, die sie für bestimmte Anwendungsfälle ungeeignet machen. Insbesondere bieten f-Strings keine Möglichkeit, interpolierte Werte abzufangen und zu transformieren, bevor sie zu einem endgültigen String kombiniert werden.
Infolgedessen kann die unvorsichtige Verwendung von f-Strings zu Sicherheitslücken führen. Ein Benutzer, der eine SQL-Abfrage mit sqlite3 ausführt, könnte versucht sein, einen f-String zu verwenden, um Werte in seinen SQL-Ausdruck einzubetten, was zu einem SQL-Injection-Angriff führen könnte. Oder ein Entwickler, der HTML erstellt, könnte unbereinigte Benutzereingaben in den String aufnehmen, was zu einer Cross-Site-Scripting (XSS)-Schwachstelle führen würde.
Allgemeiner gesagt, die Unfähigkeit, interpolierte Werte zu transformieren, bevor sie zu einem endgültigen String kombiniert werden, schränkt den Nutzen von f-Strings bei komplexeren String-Verarbeitungsaufgaben ein.
Template-Strings lösen diese Probleme, indem sie Entwicklern Zugriff auf den String und seine interpolierten Werte geben.
Stellen Sie sich zum Beispiel vor, wir möchten etwas HTML generieren. Mit Template-Strings können wir eine html()-Funktion definieren, die es uns ermöglicht, Inhalte automatisch zu bereinigen
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
assert html(template) == "<p><script>alert('evil')</script></p>"
Ebenso kann unsere hypothetische html()-Funktion es Entwicklern leicht machen, Attribute zu HTML-Elementen über ein Wörterbuch hinzuzufügen
attributes = {"src": "shrubbery.jpg", "alt": "looks nice"}
template = t"<img {attributes} />"
assert html(template) == '<img src="shrubbery.jpg" alt="looks nice" />'
Keines dieser Beispiele ist mit f-Strings möglich. Durch die Bereitstellung eines Mechanismus zum Abfangen und Transformieren interpolierter Werte ermöglichen Template-Strings eine Vielzahl von String-Verarbeitungsanwendungsfällen.
Spezifikation
Template-String-Literale
Diese PEP führt ein neues String-Präfix, t, ein, um Template-String-Literale zu definieren. Diese Literale werden zu einem neuen Typ, Template, ausgewertet, der sich im Standardbibliothekmodul string.templatelib befindet.
Der folgende Code erstellt eine Template-Instanz
from string.templatelib import Template
template = t"This is a template string."
assert isinstance(template, Template)
Template-String-Literale unterstützen die vollständige Syntax von PEP 701. Dies schließt die Möglichkeit ein, Template-Strings innerhalb von Interpolationen zu verschachteln, sowie die Möglichkeit, alle gültigen Anführungszeichen zu verwenden (', ", ''' und """). Wie andere String-Präfixe muss das t-Präfix unmittelbar dem Anführungszeichen vorangehen. Wie bei f-Strings werden sowohl Klein- als auch Großbuchstaben-Präfixe t und T unterstützt. Wie bei f-Strings dürfen t-Strings nicht mit u oder dem b-Präfix kombiniert werden.
Zusätzlich können f-Strings und t-Strings nicht kombiniert werden, daher ist das Präfix ft ungültig. t-Strings *können* mit dem r-Präfix kombiniert werden; siehe den Abschnitt Rohe Template-Strings unten für weitere Informationen.
Der Typ Template
Template-Strings werden zu einer Instanz eines neuen unveränderlichen Typs, string.templatelib.Template, ausgewertet.
class Template:
strings: tuple[str, ...]
"""
A non-empty tuple of the string parts of the template,
with N+1 items, where N is the number of interpolations
in the template.
"""
interpolations: tuple[Interpolation, ...]
"""
A tuple of the interpolation parts of the template.
This will be an empty tuple if there are no interpolations.
"""
def __new__(cls, *args: str | Interpolation):
"""
Create a new Template instance.
Arguments can be provided in any order.
"""
...
@property
def values(self) -> tuple[object, ...]:
"""
Return a tuple of the `value` attributes of each Interpolation
in the template.
This will be an empty tuple if there are no interpolations.
"""
...
def __iter__(self) -> Iterator[str | Interpolation]:
"""
Iterate over the string parts and interpolations in the template.
These may appear in any order. Empty strings will not be included.
"""
...
Die Attribute strings und interpolations bieten Zugriff auf die String-Teile und eventuelle Interpolationen im Literal.
name = "World"
template = t"Hello {name}"
assert template.strings[0] == "Hello "
assert template.interpolations[0].value == "World"
Der Typ Interpolation
Der Typ Interpolation repräsentiert einen Ausdruck innerhalb eines Template-Strings. Wie Template ist dies eine neue Klasse, die im Modul string.templatelib zu finden ist.
class Interpolation:
value: object
expression: str
conversion: Literal["a", "r", "s"] | None
format_spec: str
__match_args__ = ("value", "expression", "conversion", "format_spec")
def __new__(
cls,
value: object,
expression: str = "",
conversion: Literal["a", "r", "s"] | None = None,
format_spec: str = "",
):
...
Der Typ Interpolation ist flach unveränderlich. Seine Attribute können nicht neu zugewiesen werden.
Das Attribut value ist das ausgewertete Ergebnis der Interpolation.
name = "World"
template = t"Hello {name}"
assert template.interpolations[0].value == "World"
Wenn Interpolationen aus einem Template-String-Literal erstellt werden, enthält das Attribut expression den *ursprünglichen Text* der Interpolation.
name = "World"
template = t"Hello {name}"
assert template.interpolations[0].expression == "name"
Wenn Entwickler explizit eine Interpolation konstruieren, können sie optional einen Wert für das Attribut expression angeben. Obwohl es als String gespeichert wird, sollte dies ein gültiger Python-Ausdruck sein. Wenn kein Wert angegeben wird, ist das Attribut expression standardmäßig der leere String ("").
Wir erwarten, dass das Attribut expression in den meisten Template-Verarbeitungscodes nicht verwendet wird. Es dient der Vollständigkeit und zur Verwendung beim Debugging und bei der Introspektion. Siehe sowohl den Abschnitt Häufige Muster bei der Verarbeitung von Templates als auch den Abschnitt Beispiele für weitere Informationen zur Verarbeitung von Template-Strings.
Das Attribut conversion ist die optionale Konvertierung, die verwendet werden soll, eine von r, s und a, entsprechend repr(), str() und ascii() Konvertierungen. Wie bei f-Strings werden keine anderen Konvertierungen unterstützt.
name = "World"
template = t"Hello {name!r}"
assert template.interpolations[0].conversion == "r"
Wenn keine Konvertierung angegeben wird, ist conversion None.
Das Attribut format_spec ist die Format-Spezifikation. Wie bei f-Strings ist dies ein beliebiger String, der definiert, wie der Wert dargestellt werden soll.
value = 42
template = t"Value: {value:.2f}"
assert template.interpolations[0].format_spec == ".2f"
Format-Spezifikationen in f-Strings können selbst Interpolationen enthalten. Dies ist auch in Template-Strings erlaubt; format_spec wird auf das sofort ausgewertete Ergebnis gesetzt.
value = 42
precision = 2
template = t"Value: {value:.{precision}f}"
assert template.interpolations[0].format_spec == ".2f"
Wenn keine Format-Spezifikation angegeben wird, ist format_spec standardmäßig ein leerer String (""). Dies entspricht dem Parameter format_spec der Python-internen Funktion format().
Im Gegensatz zu f-Strings liegt es am Code, der das Template verarbeitet, festzulegen, wie die Attribute conversion und format_spec interpretiert werden. Solcher Code ist nicht verpflichtet, diese Attribute zu verwenden, aber wenn sie vorhanden sind, sollten sie berücksichtigt werden, und zwar im weitest möglichen Umfang im Einklang mit dem Verhalten von f-Strings. Es wäre beispielsweise überraschend, wenn ein Template-String, der {value:.2f} verwendet, beim Verarbeiten nicht auf zwei Dezimalstellen gerundet würde.
Die Template.values Eigenschaft
Die Eigenschaft Template.values ist eine Abkürzung für den Zugriff auf das Attribut value jeder Interpolation im Template und entspricht
@property
def values(self) -> tuple[object, ...]:
return tuple(i.value for i in self.interpolations)
Iterieren über Template Inhalte
Die Methode Template.__iter__() bietet eine einfache Möglichkeit, auf den vollständigen Inhalt eines Templates zuzugreifen. Sie liefert die String-Teile und Interpolationen in der Reihenfolge ihres Erscheinens, wobei leere Strings weggelassen werden.
Die Methode __iter__() entspricht
def __iter__(self) -> Iterator[str | Interpolation]:
for s, i in zip_longest(self.strings, self.interpolations):
if s:
yield s
if i:
yield i
Die folgenden Beispiele zeigen die Methode __iter__() in Aktion.
assert list(t"") == []
assert list(t"Hello") == ["Hello"]
name = "World"
template = t"Hello {name}!"
contents = list(template)
assert len(contents) == 3
assert contents[0] == "Hello "
assert contents[1].value == "World"
assert contents[1].expression == "name"
assert contents[2] == "!"
Leere Strings, die in Template.strings vorhanden sein können, werden nicht in der Ausgabe der Methode __iter__() enthalten sein.
first = "Eat"
second = "Red Leicester"
template = t"{first}{second}"
contents = list(template)
assert len(contents) == 2
assert contents[0].value == "Eat"
assert contents[0].expression == "first"
assert contents[1].value == "Red Leicester"
assert contents[1].expression == "second"
# However, the strings attribute contains empty strings:
assert template.strings == ("", "", "")
Template-Verarbeitungscode kann wählen, mit jeder beliebigen Kombination von strings, interpolations, values und __iter__() zu arbeiten, je nach Anforderungen und Bequemlichkeit.
Verarbeitung von Template-Strings
Entwickler können beliebigen Code schreiben, um Template-Strings zu verarbeiten. Die folgende Funktion rendert beispielsweise statische Teile des Templates in Kleinbuchstaben und Interpolationen in Großbuchstaben.
from string.templatelib import Template, Interpolation
def lower_upper(template: Template) -> str:
"""Render static parts lowercased and interpolations uppercased."""
parts: list[str] = []
for item in template:
if isinstance(item, Interpolation):
parts.append(str(item.value).upper())
else:
parts.append(item.lower())
return "".join(parts)
name = "world"
assert lower_upper(t"HELLO {name}") == "hello WORLD"
Es gibt keine Anforderung, dass Template-Strings auf eine bestimmte Weise verarbeitet werden müssen. Code, der Templates verarbeitet, ist nicht verpflichtet, einen String zurückzugeben. Template-Strings sind ein flexibles, universell einsetzbares Feature.
Siehe den Abschnitt Häufige Muster bei der Verarbeitung von Templates für weitere Informationen zur Verarbeitung von Template-Strings. Siehe den Abschnitt Beispiele für detaillierte funktionierende Beispiele.
Verkettung von Template-Strings
Template-Strings unterstützen explizite Verkettung mit +. Die Verkettung wird für zwei Template-Instanzen über Template.__add__() unterstützt.
name = "World"
assert isinstance(t"Hello " + t"{name}", Template)
assert (t"Hello " + t"{name}").strings == ("Hello ", "")
assert (t"Hello " + t"{name}").values[0] == "World"
Implizite Verkettung von zwei Template-String-Literalen wird ebenfalls unterstützt.
name = "World"
assert isinstance(t"Hello " t"{name}", Template)
assert (t"Hello " t"{name}").strings == ("Hello ", "")
assert (t"Hello " t"{name}").values[0] == "World"
Sowohl implizite als auch explizite Verkettung von Template und str ist untersagt. Dies liegt daran, dass unklar ist, ob der str als statischer String-Teil oder als Interpolation behandelt werden soll.
Um ein Template und einen str zu kombinieren, müssen Entwickler explizit entscheiden, wie der str behandelt werden soll. Wenn der str als statischer String-Teil gedacht ist, sollte er in ein Template verpackt werden. Wenn der str als Interpolationswert gedacht ist, sollte er in eine Interpolation verpackt und an den Template-Konstruktor übergeben werden. Zum Beispiel:
name = "World"
# Treat `name` as a static string part
template = t"Hello " + Template(name)
# Treat `name` as an interpolation
template = t"Hello " + Template(Interpolation(name, "name"))
Gleichheit von Template und Interpolation
Template- und Interpolation-Instanzen vergleichen sich anhand ihrer Objektidentität (is).
Template-Instanzen sind für die Verwendung durch Template-Verarbeitungscode bestimmt, der einen String oder jeden anderen Typ zurückgeben kann. Diese Typen können nach Bedarf ihre eigenen Gleichheitssemantiken bereitstellen.
Keine Unterstützung für Ordering
Die Typen Template und Interpolation unterstützen keine Ordnung. Dies unterscheidet sie von allen anderen String-Literal-Typen in Python, die eine lexikografische Ordnung unterstützen. Da Interpolationen beliebige Werte enthalten können, gibt es keine natürliche Ordnung für sie. Folglich implementiert weder der Typ Template noch der Typ Interpolation die Standardvergleichsmethoden.
Unterstützung für den Debug-Spezifizierer (=)
Der Debug-Spezifizierer, =, wird in Template-Strings unterstützt und verhält sich ähnlich wie in f-Strings, obwohl es aufgrund von Implementierungsbeschränkungen einen geringfügigen Unterschied gibt.
Insbesondere wird t'{value=}' als t'value={value!r}' behandelt. Der erste statische String wird von "" zu "value=" umgeschrieben und die conversion ist standardmäßig r.
name = "World"
template = t"Hello {name=}"
assert template.strings[0] == "Hello name="
assert template.interpolations[0].value == "World"
assert template.interpolations[0].conversion == "r"
Wenn eine Konvertierung explizit angegeben wird, bleibt sie erhalten: t'{value=!s}' wird als t'value={value!s}' behandelt.
Wenn eine Format-Spezifikation ohne Konvertierung angegeben wird, wird conversion auf None gesetzt: t'{value=:fmt}' wird als t'value={value:fmt}' behandelt.
Leerzeichen werden im Debug-Spezifizierer beibehalten, so dass t'{value = }' als t'value = {value!r}' behandelt wird.
Rohe Template-Strings
Rohe Template-Strings werden mit dem Präfix rt (oder tr) unterstützt.
trade = 'shrubberies'
template = rt'Did you say "{trade}"?\n'
assert template.strings[0] == r'Did you say "'
assert template.strings[1] == r'"?\n'
In diesem Beispiel wird \n als zwei separate Zeichen (ein Backslash gefolgt von 'n') und nicht als Zeilenumbruchzeichen behandelt. Dies entspricht dem Verhalten von rohen Strings in Python.
Wie bei regulären Template-Strings werden Interpolationen in rohen Template-Strings normal verarbeitet, was die Kombination von Roh-String-Verhalten und dynamischem Inhalt ermöglicht.
Auswertung von Interpolationsausdrücken
Die Auswertung von Ausdrücken für Interpolationen ist dieselbe wie in PEP 498.
Die aus dem String extrahierten Ausdrücke werden im Kontext ausgewertet, in dem der Template-String aufgetreten ist. Das bedeutet, dass der Ausdruck vollen Zugriff auf seinen lexikalischen Geltungsbereich hat, einschließlich lokaler und globaler Variablen. Jeder gültige Python-Ausdruck kann verwendet werden, einschließlich Funktions- und Methodenaufrufen.
Template-Strings werden, genau wie f-Strings, vom links nach rechts eager ausgewertet. Das bedeutet, dass Interpolationen sofort ausgewertet werden, wenn der Template-String verarbeitet wird, nicht verzögert oder in Lambdas verpackt.
Ausnahmen
Ausnahmen, die in t-String-Literalen ausgelöst werden, sind dieselben wie die, die in f-String-Literalen ausgelöst werden.
Keine Template.__str__() Implementierung
Der Typ Template bietet keine spezialisierte __str__()-Implementierung.
Dies liegt daran, dass Template-Instanzen für die Verwendung durch Template-Verarbeitungscode bestimmt sind, der einen String oder jeden anderen Typ zurückgeben kann. Es gibt keine kanonische Methode, eine Template in einen String zu konvertieren.
Die Typen Template und Interpolation bieten beide nützliche __repr__()-Implementierungen.
Das Modul string.templatelib
Das Modul string (string) wird in ein Paket umgewandelt, mit einem neuen Untermodul templatelib, das die Typen Template und Interpolation enthält. Nach der Implementierung dieser PEP kann dieses neue Modul für verwandte Funktionen wie convert() oder potenziellen zukünftigen Template-Verarbeitungscode wie Shell-Skript-Helfer verwendet werden.
Beispiele
Alle Beispiele in diesem Abschnitt der PEP haben vollständig getestete Referenzimplementierungen, die im öffentlichen pep750-examples Git-Repository verfügbar sind.
Beispiel: Implementierung von f-Strings mit t-Strings
Es ist einfach, f-Strings mit t-Strings zu „implementieren“. Das heißt, wir können eine Funktion f(template: Template) -> str schreiben, die ein Template auf ähnliche Weise wie ein f-String-Literal verarbeitet und dasselbe Ergebnis liefert.
name = "World"
value = 42
templated = t"Hello {name!r}, value: {value:.2f}"
formatted = f"Hello {name!r}, value: {value:.2f}"
assert f(templated) == formatted
Die Funktion f() unterstützt sowohl Konvertierungsspezifizierer wie !r als auch Format-Spezifizierer wie :.2f. Der vollständige Code ist recht einfach.
from string.templatelib import Template, Interpolation
def convert(value: object, conversion: Literal["a", "r", "s"] | None) -> object:
if conversion == "a":
return ascii(value)
elif conversion == "r":
return repr(value)
elif conversion == "s":
return str(value)
return value
def f(template: Template) -> str:
parts = []
for item in template:
match item:
case str() as s:
parts.append(s)
case Interpolation(value, _, conversion, format_spec):
value = convert(value, conversion)
value = format(value, format_spec)
parts.append(value)
return "".join(parts)
Beispiel: Strukturierte Protokollierung
Strukturierte Protokollierung ermöglicht es Entwicklern, Daten in maschinenlesbaren Formaten wie JSON zu protokollieren. Mit t-Strings können Entwickler strukturierte Daten problemlos neben menschenlesbaren Nachrichten mit nur einer einzigen Protokollanweisung protokollieren.
Wir stellen zwei verschiedene Ansätze zur Implementierung strukturierter Protokollierung mit Template-Strings vor.
Ansatz 1: Benutzerdefinierte Protokollnachrichten
Das Python Logging Cookbook enthält einen kurzen Abschnitt darüber, wie strukturierte Protokollierung implementiert werden kann.
Das Logging Cookbook schlägt vor, eine neue „Nachrichten“-Klasse, StructuredMessage, zu erstellen, die mit einer einfachen Textnachricht und einem separaten Wörterbuch von Werten konstruiert wird.
message = StructuredMessage("user action", {
"action": "traded",
"amount": 42,
"item": "shrubs"
})
logging.info(message)
# Outputs:
# user action >>> {"action": "traded", "amount": 42, "item": "shrubs"}
Die Methode StructuredMessage.__str__() formatiert sowohl die menschenlesbare Nachricht *als auch* die Werte und kombiniert sie zu einem endgültigen String. (Siehe das Logging Cookbook für dessen vollständiges Beispiel.)
Wir können eine verbesserte Version von StructuredMessage mit Template-Strings implementieren.
import json
from string.templatelib import Interpolation, Template
from typing import Mapping
class TemplateMessage:
def __init__(self, template: Template) -> None:
self.template = template
@property
def message(self) -> str:
# Use the f() function from the previous example
return f(self.template)
@property
def values(self) -> Mapping[str, object]:
return {
item.expression: item.value
for item in self.template
if isinstance(item, Interpolation)
}
def __str__(self) -> str:
return f"{self.message} >>> {json.dumps(self.values)}"
_ = TemplateMessage # optional, to improve readability
action, amount, item = "traded", 42, "shrubs"
logging.info(_(t"User {action}: {amount:.2f} {item}"))
# Outputs:
# User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}
Template-Strings bieten uns eine elegantere Möglichkeit, die benutzerdefinierte Nachrichtenklasse zu definieren. Mit Template-Strings ist es für Entwickler nicht mehr notwendig, sicherzustellen, dass ihre Format-String und ihr Werte-Wörterbuch synchron gehalten werden; ein einziger Template-String-Literal ist alles, was benötigt wird. Die Implementierung TemplateMessage kann strukturierte Schlüssel und Werte automatisch aus den Attributen Interpolation.expression und Interpolation.value extrahieren.
Ansatz 2: Benutzerdefinierte Formatter
Benutzerdefinierte Nachrichten sind ein vernünftiger Ansatz für strukturierte Protokollierung, können aber etwas umständlich sein. Um sie zu verwenden, müssen Entwickler jede von ihnen geschriebene Protokollnachricht in eine benutzerdefinierte Klasse verpacken. Das kann leicht vergessen werden.
Ein alternativer Ansatz besteht darin, benutzerdefinierte logging.Formatter-Klassen zu definieren. Dieser Ansatz ist flexibler und ermöglicht mehr Kontrolle über die endgültige Ausgabe. Insbesondere ist es möglich, einen einzigen Template-String zu nehmen und ihn in mehreren Formaten (menschenlesbar und JSON) an separate Protokollstreams auszugeben.
Wir definieren zwei einfache Formatter, einen MessageFormatter für menschenlesbare Ausgabe und einen ValuesFormatter für JSON-Ausgabe.
import json
from logging import Formatter, LogRecord
from string.templatelib import Interpolation, Template
from typing import Any, Mapping
class MessageFormatter(Formatter):
def message(self, template: Template) -> str:
# Use the f() function from the previous example
return f(template)
def format(self, record: LogRecord) -> str:
msg = record.msg
if not isinstance(msg, Template):
return super().format(record)
return self.message(msg)
class ValuesFormatter(Formatter):
def values(self, template: Template) -> Mapping[str, Any]:
return {
item.expression: item.value
for item in template
if isinstance(item, Interpolation)
}
def format(self, record: LogRecord) -> str:
msg = record.msg
if not isinstance(msg, Template):
return super().format(record)
return json.dumps(self.values(msg))
Wir können diese Formatter dann bei der Konfiguration unseres Loggers verwenden.
import logging
import sys
logger = logging.getLogger(__name__)
message_handler = logging.StreamHandler(sys.stdout)
message_handler.setFormatter(MessageFormatter())
logger.addHandler(message_handler)
values_handler = logging.StreamHandler(sys.stderr)
values_handler.setFormatter(ValuesFormatter())
logger.addHandler(values_handler)
action, amount, item = "traded", 42, "shrubs"
logger.info(t"User {action}: {amount:.2f} {item}")
# Outputs to sys.stdout:
# User traded: 42.00 shrubs
# At the same time, outputs to sys.stderr:
# {"action": "traded", "amount": 42, "item": "shrubs"}
Dieser Ansatz hat ein paar Vorteile gegenüber dem benutzerdefinierten Nachrichtenansatz für strukturierte Protokollierung:
- Entwickler können einen t-String direkt protokollieren, ohne ihn in eine benutzerdefinierte Klasse zu verpacken.
- Menschenlesbare und strukturierte Ausgaben können an separate Protokollstreams gesendet werden. Dies ist nützlich für Log-Aggregationssysteme, die strukturierte Daten unabhängig von menschenlesbaren Daten verarbeiten.
Beispiel: HTML-Templating
Diese PEP enthält mehrere kurze Beispiele für HTML-Templating. Es stellt sich heraus, dass die „hypothetische“ html()-Funktion, die im Abschnitt Motivation (und an einigen anderen Stellen dieser PEP) erwähnt wurde, existiert und im pep750-examples Repository verfügbar ist. Wenn Sie über das Parsen einer komplexen Grammatik mit Template-Strings nachdenken, hoffen wir, dass Sie sie nützlich finden werden.
Abwärtskompatibilität
Wie bei f-Strings stellt die Verwendung von Template-Strings eine syntaktische Rückwärtsinkompatibilität mit früheren Versionen dar.
Sicherheitsimplikationen
Die Sicherheitsimplikationen der Arbeit mit Template-Strings in Bezug auf Interpolationen sind wie folgt:
- Die Bereichssuche ist dieselbe wie bei f-Strings (lexikalischer Bereich). Dieses Modell hat sich in der Praxis gut bewährt.
- Code, der
Template-Instanzen verarbeitet, kann sicherstellen, dass Interpolationen sicher verarbeitet werden, einschließlich der Berücksichtigung des Kontexts, in dem sie erscheinen.
Wie man das lehrt
Template-Strings haben mehrere Zielgruppen:
- Entwickler, die Template-Strings und Verarbeitungsfunktionen verwenden
- Autoren von Template-Verarbeitungscode
- Framework-Autoren, die interessante Mechanismen mit Template-Strings aufbauen
Wir hoffen, dass das Lehren der Template-Strings für Entwickler unkompliziert sein wird. Auf den ersten Blick sehen Template-Strings genauso aus wie f-Strings. Ihre Syntax ist vertraut und die Geltungsbereichsregeln bleiben gleich.
Das erste, was Entwickler lernen müssen, ist, dass Template-String-Literale nicht zu Strings ausgewertet werden; stattdessen werden sie zu einem neuen Typ, Template, ausgewertet. Dies ist ein einfacher Typ, der für die Verwendung durch Template-Verarbeitungscode bestimmt ist. Erst wenn Entwickler eine Verarbeitungsfunktion aufrufen, erhalten sie das gewünschte Ergebnis: typischerweise einen String, obwohl der Verarbeitungscode natürlich jeden beliebigen Typ zurückgeben kann.
Entwickler werden auch verstehen wollen, wie sich Template-Strings zu anderen String-Formatierungsmethoden wie f-Strings und str.format() verhalten. Sie werden entscheiden müssen, wann jede Methode verwendet werden soll. Wenn nur ein einfacher String benötigt wird und keine Sicherheitsimplikationen bestehen, sind f-Strings wahrscheinlich die beste Wahl. Für die meisten Fälle, in denen ein Format-String verwendet wird, kann dieser durch eine Funktion ersetzt werden, die die Erstellung eines Template-Strings umschließt. In Fällen, in denen der Format-String aus Benutzereingaben, dem Dateisystem oder Datenbanken stammt, ist es möglich, Code zu schreiben, um ihn nach Wunsch in eine Template-Instanz zu konvertieren.
Da Entwickler lernen werden, dass t-Strings fast immer in Verbindung mit Verarbeitungsfunktionen verwendet werden, müssen sie nicht unbedingt die Details des Template-Typs verstehen. Wie bei Deskriptoren und Dekoratoren erwarten wir, dass viele Entwickler t-Strings verwenden werden, anstatt t-String-Verarbeitungsfunktionen zu schreiben.
Im Laufe der Zeit werden einige fortgeschrittenere Entwickler ihre eigenen Template-Verarbeitungscodes erstellen wollen. Das Schreiben von Verarbeitungscode erfordert oft das Denken in Bezug auf formale Grammatiken. Entwickler müssen lernen, wie sie mit den Attributen strings und interpolation einer Template-Instanz arbeiten und wie sie Interpolationen kontextabhängig verarbeiten können. Anspruchsvollere Grammatiken erfordern wahrscheinlich das Parsen zu Zwischenrepräsentationen wie einem abstrakten Syntaxbaum (AST). Großartiger Template-Verarbeitungscode wird Format-Spezifikationen und Konvertierungen berücksichtigen, wenn dies angemessen ist. Das Schreiben von produktionsreifen Template-Verarbeitungscodes – zum Beispiel zur Unterstützung von HTML-Templates – kann eine große Aufgabe sein.
Wir erwarten, dass Template-Strings Framework-Autoren ein leistungsstarkes neues Werkzeug in ihrem Werkzeugkasten bieten werden. Während die Funktionalität von Template-Strings mit bestehenden Werkzeugen wie Template-Engines überlappt, verlagern t-Strings diese Logik in die Sprache selbst. Die volle Kraft und Allgemeinheit von Python auf String-Verarbeitungsaufgaben anzuwenden, eröffnet neue Möglichkeiten für Framework-Autoren.
Warum ein anderer Templating-Ansatz?
Die Welt von Python hat bereits ausgereifte Templating-Sprachen mit breiter Akzeptanz, wie z.B. Jinja. Warum Unterstützung für die Erstellung neuer Templating-Systeme schaffen?
Projekte wie Jinja werden immer noch in Fällen benötigt, in denen das Template weniger Teil der Software der Entwickler ist, sondern eher Teil der Anpassung durch Designer oder sogar von Benutzern erstellter Inhalte, z.B. in einem CMS.
Die Trends in der Frontend-Entwicklung behandeln Templating als Teil der Software und von Entwicklern geschrieben. Sie wünschen sich moderne Sprachfeatures und eine gute Werkzeugunterstützung. PEP 750 sieht DSLs vor, bei denen die nicht-statischen Teile Python sind: dieselben Geltungsbereichsregeln, Typisierung, Ausdruckssyntax und Ähnliches.
Häufige Muster bei der Verarbeitung von Templates
Strukturelle Mustererkennung
Das Iterieren über das Template mit strukturellem Pattern Matching ist die erwartete Best Practice für viele Implementierungen von Template-Funktionen.
from string.templatelib import Template, Interpolation
def process(template: Template) -> Any:
for item in template:
match item:
case str() as s:
... # handle each string part
case Interpolation() as interpolation:
... # handle each interpolation
Verarbeitungscode kann auch häufig Unter-Matches auf Attributen des Typs Interpolation durchführen.
match arg:
case Interpolation(int()):
... # handle interpolations with integer values
case Interpolation(value=str() as s):
... # handle interpolations with string values
# etc.
Memoization
Template-Funktionen können sowohl statische als auch dynamische Teile von Templates effizient verarbeiten. Die Struktur von Template-Objekten ermöglicht eine effektive Memoization.
strings = template.strings # Static string parts
values = template.values # Dynamic interpolated values
Diese Trennung ermöglicht das Caching von verarbeiteten statischen Teilen, während dynamische Teile nach Bedarf eingefügt werden können. Autoren von Template-Verarbeitungscode können die statischen strings als Cache-Schlüssel verwenden, was zu erheblichen Leistungsverbesserungen führt, wenn ähnliche Templates wiederholt verwendet werden.
Parsen zu Zwischenrepräsentationen
Code, der Templates verarbeitet, kann den Template-String in Zwischenrepräsentationen parsen, wie z.B. einen AST. Wir erwarten, dass viele Template-Verarbeitungsbibliotheken diesen Ansatz verwenden werden.
Zum Beispiel könnte unsere theoretische Funktion html() anstelle der Rückgabe eines str ein HTML Element zurückgeben, das im selben Paket definiert ist (siehe Abschnitt Motivation).
@dataclass(frozen=True)
class Element:
tag: str
attributes: Mapping[str, str | bool]
children: Sequence[str | Element]
def __str__(self) -> str:
...
def html(template: Template) -> Element:
...
Das Aufrufen von str(element) würde dann das HTML rendern, aber in der Zwischenzeit könnte das Element auf vielfältige Weise manipuliert werden.
Kontextabhängige Verarbeitung von Interpolationen
Fortfahrend mit unserer hypothetischen Funktion html() könnte sie kontextabhängig gemacht werden. Interpolationen könnten je nachdem, wo sie in der Vorlage erscheinen, unterschiedlich verarbeitet werden.
Zum Beispiel könnte unsere Funktion html() mehrere Arten von Interpolationen unterstützen
attributes = {"id": "main"}
attribute_value = "shrubbery"
content = "hello"
template = t"<div {attributes} data-value={attribute_value}>{content}</div>"
element = html(template)
assert str(element) == '<div id="main" data-value="shrubbery">hello</div>'
Da die Interpolation {attributes} im Kontext eines HTML-Tags auftritt und kein entsprechendes Attribut gefunden wird, wird sie als Wörterbuch von Attributen behandelt. Die Interpolation {attribute_value} wird als einfacher Zeichenkettenwert behandelt und vor der Aufnahme in die endgültige Zeichenkette maskiert. Die Interpolation {content} wird als potenziell unsicherer Inhalt behandelt und vor der Aufnahme in die endgültige Zeichenkette maskiert.
Verschachtelte Template-Strings
Wenn wir mit unserer Funktion html() einen Schritt weiter gehen, könnten wir verschachtelte Vorlagenzeichenketten unterstützen. Dies würde es ermöglichen, komplexere HTML-Strukturen aus einfacheren Vorlagen aufzubauen.
name = "World"
content = html(t"<p>Hello {name}</p>")
template = t"<div>{content}</div>"
element = html(template)
assert str(element) == '<div><p>Hello World</p></div>'
Da die Interpolation {content} eine Instanz von Element ist, muss sie vor der Aufnahme in die endgültige Zeichenkette nicht maskiert werden.
Man könnte sich eine schöne Vereinfachung vorstellen: Wenn der Funktion html() eine Instanz von Template übergeben wird, könnte sie diese automatisch in ein Element umwandeln, indem sie sich selbst rekursiv auf der verschachtelten Vorlage aufruft.
Wir erwarten, dass Verschachtelung und Komposition von Vorlagen ein gängiges Muster im Verarbeitungscode von Vorlagen sein werden und wo angebracht, gegenüber einfacher Zeichenkettenverkettung bevorzugt werden.
Ansätze zur verzögerten Auswertung
Ähnlich wie f-strings werden Interpolationen in t-string-Literalen sofort ausgewertet. Es gibt jedoch Fälle, in denen eine verzögerte Auswertung wünschenswert ist.
Wenn die Auswertung einer einzelnen Interpolation aufwändig ist, kann sie in der Vorlagenzeichenkette explizit in ein lambda eingeschlossen werden.
name = "World"
template = t"Hello {(lambda: name)}"
assert callable(template.interpolations[0].value)
assert template.interpolations[0].value() == "World"
Dies setzt natürlich voraus, dass der Verarbeitungscode der Vorlage aufrufbare Interpolationswerte erwartet und verarbeitet. (Man könnte sich auch vorstellen, Iteratoren, Awaitables usw. zu unterstützen.) Dies ist keine Anforderung des PEP, aber ein gängiges Muster im Verarbeitungscode von Vorlagen.
Im Allgemeinen hoffen wir, dass die Community Best Practices für die verzögerte Auswertung von Interpolationen in Vorlagenzeichenketten entwickeln wird und dass, wo es sinnvoll ist, gängige Bibliotheken die Unterstützung für aufrufbare oder awaitable Werte in ihrem Vorlagenverarbeitungscode bieten werden.
Ansätze zur asynchronen Auswertung
Eng verwandt mit der verzögerten Auswertung ist die asynchrone Auswertung.
Wie bei f-strings ist das Schlüsselwort await in Interpolationen erlaubt.
async def example():
async def get_name() -> str:
await asyncio.sleep(1)
return "Sleepy"
template = t"Hello {await get_name()}"
# Use the f() function from the f-string example, above
assert f(template) == "Hello Sleepy"
Anspruchsvollerer Vorlagenverarbeitungscode kann dies nutzen, um asynchrone Operationen in Interpolationen durchzuführen. Zum Beispiel könnte eine "intelligente" Verarbeitungsfunktion erkennen, dass eine Interpolation awaitable ist, und sie abwarten, bevor sie die Vorlagenzeichenkette verarbeitet.
async def example():
async def get_name() -> str:
await asyncio.sleep(1)
return "Sleepy"
template = t"Hello {get_name}"
assert await async_f(template) == "Hello Sleepy"
Dies setzt voraus, dass der Vorlagenverarbeitungscode in async_f() asynchron ist und den Wert einer Interpolation awaiten kann.
Ansätze zur Wiederverwendung von Templates
Wenn Entwickler Vorlagenzeichenketten mehrmals mit unterschiedlichen Werten wiederverwenden möchten, können sie eine Funktion schreiben, die eine Instanz von Template zurückgibt.
def reusable(name: str, question: str) -> Template:
return t"Hello {name}, {question}?"
template = reusable("friend", "how are you")
template = reusable("King Arthur", "what is your quest")
Dies ist natürlich nicht anders als bei der Wiederverwendung von f-strings.
Beziehung zu Format-Strings
Die ehrwürdige Methode str.format() akzeptiert Formatzeichenketten, die später zur Formatierung von Werten verwendet werden können.
alas_fmt = "We're all out of {cheese}."
assert alas_fmt.format(cheese="Red Leicester") == "We're all out of Red Leicester."
Wenn man zusammenkneift, kann man Formatzeichenketten als eine Art Funktionsdefinition betrachten. Der *Aufruf* von str.format() kann als eine Art Funktionsaufruf betrachtet werden. Das t-string-Äquivalent ist einfach die Definition einer Standard-Python-Funktion, die eine Instanz von Template zurückgibt.
def make_template(*, cheese: str) -> Template:
return t"We're all out of {cheese}."
template = make_template(cheese="Red Leicester")
# Using the f() function from the f-string example, above
assert f(template) == "We're all out of Red Leicester."
Die Funktion make_template() selbst kann als analog zur Formatzeichenkette betrachtet werden. Der Aufruf von make_template() ist analog zum Aufruf von str.format().
Natürlich ist es üblich, Formatzeichenketten aus externen Quellen wie einem Dateisystem oder einer Datenbank zu laden. Glücklicherweise ist es möglich, da Template und Interpolation einfache Python-Typen sind, eine Funktion zu schreiben, die eine alte Formatzeichenkette entgegennimmt und eine äquivalente Instanz von Template zurückgibt.
def from_format(fmt: str, /, *args: object, **kwargs: object) -> Template:
"""Parse `fmt` and return a `Template` instance."""
...
# Load this from a file, database, etc.
fmt = "We're all out of {cheese}."
template = from_format(fmt, cheese="Red Leicester")
# Using the f() function from the f-string example, above
assert f(template) == "We're all out of Red Leicester."
Dies ist ein mächtiges Muster, das es Entwicklern ermöglicht, Vorlagenzeichenketten an Stellen zu verwenden, an denen sie zuvor Formatzeichenketten verwendet hätten. Eine vollständige Implementierung von from_format() ist im Beispiel-Repository verfügbar und unterstützt die vollständige Grammatik von Formatzeichenketten.
Referenzimplementierung
Eine CPython-Implementierung von PEP 750 ist verfügbar.
Es gibt auch ein öffentliches Repository mit Beispielen und Tests, die auf der Referenzimplementierung aufbauen. Wenn Sie mit Vorlagenzeichenketten experimentieren möchten, ist dieses Repository ein großartiger Ausgangspunkt.
Abgelehnte Ideen
Dieses PEP hat mehrere bedeutende Überarbeitungen durchlaufen. Darüber hinaus wurden in Überarbeitungen von PEP 501 und in der Discourse-Diskussion einige interessante Ideen erwogen.
Wir versuchen, die bedeutendsten erwogenen und verworfenen Ideen zu dokumentieren.
Beliebige Präfixe für String-Literale
Inspiriert von den JavaScript-Tag-Vorlagenliteralen (JavaScript tagged template literals) erlaubte eine frühere Version dieses PEP die Verwendung beliebiger "Tag"-Präfixe vor Literalzeichenketten.
my_tag'Hello {name}'
Das Präfix war ein spezielles aufrufbares Objekt, eine "Tag-Funktion". Tag-Funktionen erhielten die Teile der Vorlagenzeichenkette als Argumentliste. Sie konnten die Zeichenkette dann verarbeiten und einen beliebigen Wert zurückgeben.
def my_tag(*args: str | Interpolation) -> Any:
...
Dieser Ansatz wurde aus mehreren Gründen verworfen:
- Es wurde als zu komplex angesehen, um es in voller Allgemeinheit zu implementieren. JavaScript erlaubt beliebige Ausdrücke vor einer Vorlagenzeichenkette, was eine erhebliche Herausforderung für die Implementierung in Python darstellt.
- Es schloss die zukünftige Einführung neuer Zeichenkettenpräfixe aus.
- Es schien den Namespace unnötig zu belasten.
Die Verwendung eines einzelnen t-Präfixes wurde als einfacherer, pythonischerer Ansatz gewählt und entsprach mehr der Rolle von Vorlagenzeichenketten als Verallgemeinerung von f-strings.
Verzögerte Auswertung von Interpolationen
Eine frühe Version dieses PEP schlug vor, dass Interpolationen verzögert ausgewertet werden sollten. Alle Interpolationen waren in implizite Lambdas "eingepackt". Anstatt eines sofort ausgewerteten value-Attributs hatten Interpolationen eine getvalue()-Methode, die den Wert der Interpolation auflöste.
class Interpolation:
...
_value: Callable[[], object]
def getvalue(self) -> object:
return self._value()
Dies wurde aus mehreren Gründen verworfen:
- Die überwiegende Mehrheit der Anwendungsfälle für Vorlagenzeichenketten erfordert natürlich eine sofortige Auswertung.
- Die verzögerte Auswertung wäre eine signifikante Abweichung vom Verhalten von f-strings.
- Implizite Lambda-Umhüllung führt zu Schwierigkeiten mit Typ-Annotationen und statischer Analyse.
Am wichtigsten ist, dass es in vielen Fällen, in denen eine verzögerte Auswertung gewünscht wird, praktikable (wenn auch nicht perfekte) Alternativen zur impliziten Lambda-Umhüllung gibt. Siehe den Abschnitt über Ansätze zur verzögerten Auswertung oben für weitere Informationen.
Obwohl die verzögerte Auswertung für *dieses* PEP abgelehnt wurde, hoffen wir, dass die Community die Idee weiter erforscht.
Mache Template und Interpolation zu Protokollen
Eine frühe Version dieses PEP schlug vor, dass die Typen Template und Interpolation Laufzeit-prüfbare Protokolle anstelle von Klassen sein sollten.
Letztendlich empfanden wir die Verwendung von Klassen als einfacher.
Überschriebene __eq__ und __hash__ für Template und Interpolation
Frühere Versionen dieses PEP schlugen vor, dass die Typen Template und Interpolation eigene Implementierungen von __eq__ und __hash__ haben sollten.
Templates wurden als gleich betrachtet, wenn ihre strings und interpolations gleich waren; Interpolations wurden als gleich betrachtet, wenn ihr value, expression, conversion und format_spec gleich waren. Die Interpolationshashierung ähnelte der Tupelhashierung: eine Interpolation war hashbar, wenn und nur wenn ihr value hashbar war.
Dies wurde abgelehnt, da Template.__hash__ so definiert, als Cache-Schlüssel im Vorlagenverarbeitungscode nicht nützlich war; wir waren besorgt, dass es für Entwickler verwirrend sein würde.
Durch das Entfernen dieser Implementierungen von __eq__ und __hash__ verlieren wir die Möglichkeit, Assertions wie diese zu schreiben:
name = "World"
assert t"Hello " + t"{name}" == t"Hello {name}"
Da Instanzen von Template schnell von weiterem Code verarbeitet werden sollen, empfanden wir den Nutzen dieser Assertions als begrenzt.
Ein zusätzlicher Typ Decoded
Eine frühe Version dieses PEP schlug einen zusätzlichen Typ, Decoded, vor, um die "statischen Zeichenketten"-Teile einer Vorlagenzeichenkette darzustellen. Dieser Typ leitete sich von str ab und hatte ein einziges zusätzliches Attribut raw, das den ursprünglichen Text der Zeichenkette lieferte. Wir haben dies zugunsten des einfacheren Ansatzes, plain str zu verwenden und die Kombination von r und t Präfixen zu erlauben, verworfen.
Das endgültige Zuhause für Template und Interpolation
Frühere Versionen dieses PEP schlugen vor, die Typen Template und Interpolation zu platzieren in: types, collections, collections.abc, und sogar in einem neuen Top-Level-Modul, templatelib. Die endgültige Entscheidung war, sie in string.templatelib zu platzieren.
Ermöglichen der vollständigen Rekonstruktion des ursprünglichen Template-Literals
Frühere Versionen dieses PEP versuchten, die vollständige Rekonstruktion des Textes der ursprünglichen Vorlagenzeichenkette aus einer Instanz von Template zu ermöglichen. Dies wurde als übermäßig komplex abgelehnt. Die Zuordnung zwischen der Vorlagenliteralquelle und dem zugrunde liegenden AST ist nicht eins-zu-eins und es gibt mehrere Einschränkungen in Bezug auf das Round-Tripping zum ursprünglichen Quelltext.
Erstens, Interpolation.format_spec hat standardmäßig "", wenn nicht angegeben.
value = 42
template1 = t"{value}"
template2 = t"{value:}"
assert template1.interpolations[0].format_spec == ""
assert template2.interpolations[0].format_spec == ""
Als nächstes wird der Debug-Spezifizierer, =, als Sonderfall behandelt und vor der Erstellung des AST verarbeitet. Daher ist es nicht möglich, t"{value=}" von t"value={value!r}" zu unterscheiden.
value = 42
template1 = t"{value=}"
template2 = t"value={value!r}"
assert template1.strings[0] == "value="
assert template1.interpolations[0].expression == "value"
assert template1.interpolations[0].conversion == "r"
assert template2.strings[0] == "value="
assert template2.interpolations[0].expression == "value"
assert template2.interpolations[0].conversion == "r"
Schließlich erlauben Format-Spezifizierer in f-strings beliebige Verschachtelungen. In diesem PEP und in der Referenzimplementierung wird der Spezifizierer sofort ausgewertet, um den format_spec in der Interpolation zu setzen, wodurch die ursprünglichen Ausdrücke verloren gehen. Zum Beispiel:
value = 42
precision = 2
template1 = t"{value:.2f}"
template2 = t"{value:.{precision}f}"
assert template1.interpolations[0].format_spec == ".2f"
assert template2.interpolations[0].format_spec == ".2f"
Wir gehen nicht davon aus, dass diese Einschränkungen in der Praxis ein signifikantes Problem darstellen werden. Entwickler, die die ursprüngliche Vorlagenzeichenkette abrufen müssen, können jederzeit inspect.getsource() oder ähnliche Werkzeuge verwenden.
Deaktivieren der Template-Verkettung
Frühere Versionen dieses PEP schlugen vor, dass Template-Instanzen keine Verkettung unterstützen sollten. Dies wurde zugunsten der Ermöglichung der Verkettung mehrerer Template-Instanzen abgelehnt.
Es gibt vernünftige Argumente für die Ablehnung einer oder aller Formen der Verkettung: nämlich, dass sie eine Klasse von Fehlern ausschließt, insbesondere wenn man davon ausgeht, dass Vorlagenzeichenketten oft komplexe Grammatiken enthalten, für die die Verkettung nicht immer dieselbe Bedeutung (oder überhaupt Bedeutung) hat.
Darüber hinaus schlugen die frühesten Versionen dieses PEP eine Syntax vor, die näher an den JavaScript-Tag-Vorlagenliteralen lag, bei denen eine beliebige aufrufbare Funktion als Präfix einer Zeichenkettenliteral verwendet werden konnte. Es gab keine Garantie, dass die aufrufbare Funktion einen Typ zurückgeben würde, der die Verkettung unterstützte.
Letztendlich entschieden wir uns, dass die Überraschung für Entwickler eines neuen Zeichentyps, der keine Verkettung unterstützt, wahrscheinlich größer war als der theoretische Schaden, der durch seine Unterstützung verursacht wird.
Obwohl die Verkettung von zwei Templates von diesem PEP unterstützt wird, wird die Verkettung einer Template und eines str nicht unterstützt. Dies liegt daran, dass unklar ist, ob str als statische Zeichenkette oder als Interpolation behandelt werden soll. Entwickler müssen die str in eine Instanz von Template verpacken, bevor sie sie mit einer anderen Template verketten, wie oben beschrieben.
Wir erwarten, dass Code, der Vorlagenzeichenketten verwendet, größere Vorlagen eher durch Verschachtelung und Komposition als durch Verkettung aufbaut.
Beliebige Konvertierungswerte
Python erlaubt nur r, s oder a als mögliche Werte für den Konversions-Typ. Der Versuch, einen anderen Wert zuzuweisen, führt zu einem SyntaxError.
Theoretisch könnten Vorlagenfunktionen wählen, andere Konversionstypen zu behandeln. Dieses PEP hält sich jedoch eng an PEP 701. Jegliche Änderungen an erlaubten Werten sollten in einem separaten PEP erfolgen.
Entfernen von conversion aus Interpolation
Während der Erstellung dieses PEP erwogen wir, das Attribut conversion aus Interpolation zu entfernen und festzulegen, dass die Konversion sofort vor dem Setzen von Interpolation.value erfolgen sollte.
Dies wurde getan, um die Arbeit beim Schreiben von Vorlagenverarbeitungscode zu vereinfachen. Das Attribut conversion ist nur begrenzt erweiterbar (es ist als Literal["r", "s", "a"] | None typisiert). Es ist nicht klar, dass es Vorlagenzeichenketten einen signifikanten Mehrwert oder Flexibilität hinzufügt, der nicht besser durch benutzerdefinierte Format-Spezifizierer erreicht werden könnte. Im Gegensatz zu Format-Spezifizierern gibt es kein Äquivalent zur integrierten format()-Funktion von Python. (Stattdessen nehmen wir eine Beispielimplementierung von convert() in den Beispielen auf.)
Letztendlich entschieden wir uns, das Attribut conversion im Typ Interpolation beizubehalten, um die Kompatibilität mit f-strings zu wahren und zukünftige Erweiterungen zu ermöglichen.
Alternative Interpolationssymbole
In den frühen Phasen dieses PEP erwogen wir, alternative Symbole für Interpolationen in Vorlagenzeichenketten zuzulassen. Zum Beispiel erwogen wir, ${name} als Alternative zu {name} zu erlauben, mit der Idee, dass dies für i18n oder andere Zwecke nützlich sein könnte. Siehe den Discourse-Thread für weitere Informationen.
Dies wurde zugunsten der Beibehaltung der t-string-Syntax so nah wie möglich an der f-string-Syntax abgelehnt.
Alternative Layouts für Template
Während der Entwicklung dieses PEP erwogen wir mehrere alternative Layouts für den Typ Template. Viele konzentrierten sich auf ein einzelnes args-Tupel, das sowohl Zeichenketten als auch Interpolationen enthielt. Varianten umfassten:
argswar eintuple[str | Interpolation, ...]`mit der Zusage, dass seine ersten und letzten Elemente Zeichenketten waren und dass sich Zeichenketten und Interpolationen immer abwechselten. Dies implizierte, dassargsimmer nicht leer war und dass leere Zeichenketten zwischen benachbarten Interpolationen eingefügt würden. Dies wurde abgelehnt, da die Abwechselung nicht vom Typsystem erfasst werden konnte und keine Garantie war, die wir geben wollten.argsblieb eintuple[str | Interpolation, ...], unterstützte aber keine Verschachtelung. Infolgedessen wurden keine leeren Zeichenketten zur Sequenz hinzugefügt. Es war nicht mehr möglich, statische Zeichenketten mitargs[::2]zu erhalten; stattdessen mussten Instanzprüfungen oder strukturelles Pattern Matching verwendet werden, um zwischen Zeichenketten und Interpolationen zu unterscheiden. Dieser Ansatz wurde abgelehnt, da er weniger Möglichkeiten für zukünftige Leistungsoptimierungen bot.argswurde alsSequence[tuple[str, Interpolation | None]]typisiert. Jede statische Zeichenkette wurde mit ihrer benachbarten Interpolation gepaart. Der letzte Zeichenketten-Teil hatte keine entsprechende Interpolation. Dies wurde als übermäßig komplex abgelehnt.
Mechanismus zur Beschreibung der „Art“ eines Templates
Wenn t-strings sich als beliebt erweisen, könnte es nützlich sein, eine Möglichkeit zu haben, die "Art" des Inhalts in einer Vorlagenzeichenkette zu beschreiben: "sql", "html", "css" usw. Dies könnte leistungsstarke neue Funktionen in Werkzeugen wie Lintern, Formatierern, Typprüfern und IDEs ermöglichen. (Stellen Sie sich zum Beispiel vor, black würde HTML in t-strings formatieren, oder mypy würde prüfen, ob ein bestimmtes Attribut für einen HTML-Tag gültig ist.) Obwohl dies spannend ist, schlägt dieses PEP keinen spezifischen Mechanismus vor. Wir hoffen, dass die Community im Laufe der Zeit Konventionen für diesen Zweck entwickeln wird.
Binäre Template-Strings
Die Kombination von t-strings und Bytes (tb) wird für dieses PEP als außerhalb des Rahmens betrachtet. Im Gegensatz zu f-strings gibt es jedoch keinen fundamentalen Grund, warum t-strings und Bytes nicht kombiniert werden können. Eine Unterstützung könnte in einem zukünftigen PEP in Betracht gezogen werden.
Danksagungen
Dank an Ryan Morshead für Beiträge während der Entwicklung der Ideen, die zu Vorlagenzeichenketten führten. Besondere Erwähnung gebührt auch Dropbox' pyxl für die Auseinandersetzung mit ähnlichen Ideen vor Jahren. Andrea Giammarchi lieferte nachdenkliches Feedback zu den frühen Entwürfen dieses PEP. Schließlich Dank an Joachim Viide für seine Pionierarbeit an der tagged library. Tagged war nicht nur der Vorläufer von Vorlagenzeichenketten, sondern der Ort, an dem die ganze Anstrengung über einen GitHub-Issue-Kommentar begann!
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-0750.rst
Zuletzt geändert: 2025-08-17 16:08:51 GMT