PEP 501 – Allzweck-Template-Literale
- Autor:
- Alyssa Coghlan <ncoghlan at gmail.com>, Nick Humrich <nick at humrich.us>
- Discussions-To:
- Discourse thread
- Status:
- Zurückgezogen
- Typ:
- Standards Track
- Benötigt:
- 701
- Erstellt:
- 08-Aug-2015
- Python-Version:
- 3.12
- Post-History:
- 08-Aug-2015, 05-Sep-2015, 09-Mar-2023
- Ersetzt-Durch:
- 750
Inhaltsverzeichnis
- Zusammenfassung
- Rücknahme eines PEP
- Beziehung zu anderen PEPs
- Motivation
- Vorschlag
- Hintergrund
- Begründung
- Spezifikation
- Rendering von Templates
- Format-Spezifizierer
- Konvertierungsspezifizierer
- Neue Feldkonvertierungs-API im Modul
operator - Konvertierungsspezifizierer-Parameter hinzugefügt zu
format() - Strukturelles Tippen und Duck-Typing
- Schreiben benutzerdefinierter Renderer
- Auswertung von Ausdrücken
- Umgang mit Code-Injection-Angriffen
- Fehlerbehandlung
- Renderer für Shell-Escaping hinzugefügt zu
shlex - Änderungen am subprocess-Modul
- Wie man das lehrt
- Diskussion
- Unterstützung für binäre Interpolation
- Interoperabilität mit reinen str-Schnittstellen
- Beibehaltung des rohen Template-Strings
- Erzeugung eines Rich-Objekts anstelle einer globalen Namensauflösung
- Aufbauend auf f-Strings anstatt sie zu ersetzen
- Definition von Wiederholungs- und Verkettungssemantik
- Neuer Konvertierungsspezifizierer für Lazy-Feld-Evaluierung
- Zulassen beliebiger Konvertierungsspezifizierer in benutzerdefinierten Renderern
- Nur ein neuer String-Präfix reservieren
- Verschiebung der Berücksichtigung einer prägnanteren Syntax für verzögerte Auswertung
- Verschiebung der Berücksichtigung einer möglichen Protokollintegration
- Verschiebung der Berücksichtigung einer möglichen Verwendung in i18n-Anwendungsfällen
- Verschiebung der Unterstützung für escaped Rendering für Nicht-POSIX-Shells
- Danksagungen
- Referenzen
- Urheberrecht
Zusammenfassung
Obwohl einfach und elegant zu verwenden, können Python f-strings anfällig für Injection-Angriffe sein, wenn sie zum Erstellen von Shell-Befehlen, SQL-Abfragen, HTML-Snippets und Ähnlichem verwendet werden (zum Beispiel os.system(f"echo {message_from_user}")). Dieser PEP führt Template-Literal-Strings (oder „t-Strings“) ein, die eine ähnliche Syntax und Semantik wie f-Strings haben, aber das Rendering wird bis zum Aufruf von format() oder einer anderen Template-Rendering-Funktion verzögert. Dies ermöglicht es Standardbibliotheksaufrufen, Hilfsfunktionen und Drittanbietertools, Eingaben sicher und intelligent entsprechend zu escapen und andere Zeichenverarbeitung durchzuführen, während die Benutzerfreundlichkeit und Bequemlichkeit von f-Strings erhalten bleibt.
Rücknahme eines PEP
Als PEP 750 erstmals als Vorschlag für „getaggte Strings“ (die beliebige String-Präfixe erlauben) veröffentlicht wurde, blieb dieser PEP offen, um den einfacheren Ansatz der „Template-Literale“ weiter zu verfolgen, der einen einzelnen dedizierten String-Präfix verwendete, um Instanzen eines neuen Typs „Interpolation Template“ zu erzeugen.
Die Oktober 2024 Updates zu PEP 750 stimmten zu, dass Template-Strings besser für Python geeignet sind als das breitere Konzept der getaggten Strings.
Alle anderen Bedenken, die die Autoren dieses PEPs gegenüber PEP 750 hatten, wurden entweder in diesen Updates behandelt oder so belassen, dass sie in einem zukünftigen Änderungsantrag vernünftigerweise behandelt werden konnten.
Aufgrund der klaren Verbesserungen im aktualisierten PEP 750-Vorschlag wurde dieser PEP zugunsten von PEP 750 zurückgezogen.
Wichtig
Der Rest dieses PEPs spiegelt immer noch den Stand des getaggten String-Vorschlags vom August 2024 wider. Er wurde *nicht* aktualisiert, um die Änderungen vom Oktober 2024 an PEP 750 zu berücksichtigen, da die Rücknahme des PEPs dies überflüssig macht.
Beziehung zu anderen PEPs
Dieser PEP ist inspiriert von der f-String-Syntax, die zuerst in PEP 498 implementiert und in PEP 701 formalisiert wurde, und baut darauf auf.
Dieser PEP ergänzt die Unterstützung für Literal-String-Typen, die dem formalen Typsystem von Python in PEP 675 hinzugefügt wurde, indem er eine *sichere* Methode zur dynamischen Interpolation von Laufzeitwerten in sicherheitssensible Strings einführt.
Dieser PEP konkurriert mit einigen Aspekten des getaggten String-Vorschlags in PEP 750 (insbesondere in Bezug auf die Frage, ob Template-Rendering als render(t"template literal") oder als render"template literal" ausgedrückt wird), teilt aber auch *viele* gemeinsame Merkmale (nach der Veröffentlichung von PEP 750 wurde dieser PEP mit mehreren neuen Änderungen aktualisiert, die vom getaggten String-Vorschlag inspiriert wurden).
Dieser PEP schlägt NICHT eine Alternative zu PEP 292 für Internationalisierungsanwendungsfälle von Benutzeroberflächen vor (weist aber auf das Potenzial für zukünftige syntaktische Verbesserungen für diesen Anwendungsfall hin, die von der Compiler-gestützten Wertinterpolationsmaschinerie profitieren würden, die dieser PEP und PEP 750 einführen).
Motivation
PEP 498 fügte neue syntaktische Unterstützung für String-Interpolation hinzu, die für den Compiler transparent ist und Namensreferenzen aus der Interpolationsoperation vollen Zugriff auf enthaltende Namespaces (wie bei jedem anderen Ausdruck) gewährt, anstatt auf explizite Namensreferenzen beschränkt zu sein. Diese werden in dem PEP (und anderswo) als „f-Strings“ bezeichnet (eine Eselsbrücke für „formatted strings“).
Seit der Annahme von PEP 498 sind f-Strings gut etabliert und sehr beliebt geworden. f-Strings wurden mit der formalisierten Grammatik in PEP 701 noch nützlicher und flexibler. Obwohl f-Strings großartig sind, hat die eifrige (eager) Renderung ihre Grenzen. Zum Beispiel hat die Eifrigkeit von f-Strings Code wie den folgenden leider plausibel gemacht
os.system(f"echo {message_from_user}")
Diese Art von Code ist oberflächlich elegant, birgt aber ein erhebliches Problem, wenn der interpolierte Wert message_from_user tatsächlich von einem nicht vertrauenswürdigen Benutzer bereitgestellt wird: Es ist eine Einfalltür für eine Form von Code-Injection-Angriff, bei der die bereitgestellten Benutzerdaten nicht ordnungsgemäß escaped wurden, bevor sie an den os.system Aufruf übergeben wurden.
Während die in PEP 675 eingeführte LiteralString Typannotation bedeutet, dass Typechecker einen Typfehler für diese Art von unsicherer Funktionsnutzung melden können, helfen diese Fehler nicht dabei, die Erstellung von Code, der sicherere Alternativen verwendet (wie subprocess.run()), zu erleichtern.
Um dieses Problem (und eine Reihe anderer Bedenken) anzugehen, schlägt dieser PEP die komplementäre Einführung von „t-Strings“ (eine Eselsbrücke für „template literal strings“) vor, bei der format(t"Message with {data}") dasselbe Ergebnis wie f"Message with {data}" erzielen würde, aber die Template-Literal-Instanz kann stattdessen an andere Template-Rendering-Funktionen übergeben werden, die den Inhalt des Templates unterschiedlich verarbeiten.
Vorschlag
Dedizierte Template-Literal-Syntax
Dieser PEP schlägt einen neuen String-Präfix vor, der den String als Template-Literal und nicht als gewöhnlichen String deklariert
template = t"Substitute {names:>{field_width}} and {expressions()!r} at runtime"
Dies würde effektiv interpretiert als
template = TemplateLiteral(
r"Substitute {names:>{field_width}} and {expressions()} at runtime",
TemplateLiteralText(r"Substitute "),
TemplateLiteralField("names", names, f">{field_width}", ""),
TemplateLiteralText(r" and "),
TemplateLiteralField("expressions()", expressions(), f"", "r"),
)
(Hinweis: Dies ist ein illustratives Beispiel für die Implementierung. Die genaue Syntax der Compile-Zeit-Konstruktion von types.TemplateLiteral gilt als Implementierungsdetail, das nicht vom PEP spezifiziert wird. Insbesondere kann der Compiler die Laufzeitlogik des Standardkonstruktors umgehen, die aufeinanderfolgende Textsegmente erkennt und sie zu einem einzigen Textsegment zusammenfügt, sowie die Laufzeittypen aller bereitgestellten Argumente überprüft).
Die __format__ Methode auf types.TemplateLiteral würde dann die folgenden von str.format() inspirierten Semantiken implementieren
>>> import datetime
>>> name = 'Jane'
>>> age = 50
>>> anniversary = datetime.date(1991, 10, 12)
>>> format(t'My name is {name}, my age next year is {age+1}, my anniversary is {anniversary:%A, %B %d, %Y}.')
'My name is Jane, my age next year is 51, my anniversary is Saturday, October 12, 1991.'
>>> format(t'She said her name is {name!r}.')
"She said her name is 'Jane'."
Die Syntax von Template-Literalen würde auf PEP 701 basieren und weitgehend die gleiche Syntax für den String-Teil des Templates verwenden. Abgesehen von der Verwendung eines anderen Präfixes ist die einzige syntaktische Änderung die Definition und Handhabung von Konvertierungsspezifizierern, sowohl um !() als Standard-Konvertierungsspezifizierer zu erlauben, um die Auswertung eines Feldes zur Rendering-Zeit anzufordern, als auch um benutzerdefinierten Renderern zu erlauben, benutzerdefinierte Konvertierungsspezifizierer zu definieren.
Dieser PEP schlägt keine Entfernung oder Deprezierung bestehender String-Formatierungsmechanismen vor, da diese weiterhin wertvoll bleiben, wenn nicht direkt im Quellcode der Anwendung vorhandene Strings formatiert werden.
Lazy-Feld-Evaluierungs-Konvertierungsspezifizierer
Zusätzlich zur bestehenden Unterstützung für die Konvertierungsspezifizierer a, r und s werden str.format(), str.format_map() und string.Formatter aktualisiert, um () als Konvertierungsspezifizierer zu akzeptieren, der „rufe den interpolierten Wert auf“ bedeutet.
Um die Anwendung der Standard-Konvertierungsspezifizierer in benutzerdefinierten Template-Rendering-Funktionen zu unterstützen, wird eine neue Funktion operator.convert_field() hinzugefügt.
Die Signatur und das Verhalten des Built-in format() werden ebenfalls aktualisiert, um einen Konvertierungsspezifizierer als drittes optionales Argument zu akzeptieren. Wenn ein nicht-leerer Konvertierungsspezifizierer angegeben wird, wird der Wert mit operator.convert_field() konvertiert, bevor die __format__ Methode aufgerufen wird.
Benutzerdefinierte Konvertierungsspezifizierer
Um zusätzliche feldspezifische Direktiven an benutzerdefinierte Rendering-Funktionen übergeben zu können, auf eine Weise, die immer noch die Formatierung des Templates mit dem Standard-Renderer ermöglicht, darf das Feld für den Konvertierungsspezifizierer ein zweites ! Zeichen enthalten.
operator.convert_field() und format() (und damit die Standard-Rendering-Methode TemplateLiteral.render) ignorieren dieses Zeichen und jeden nachfolgenden Text im Feld des Konvertierungsspezifizierers.
str.format(), str.format_map() und string.Formatter werden ebenfalls aktualisiert, um benutzerdefinierte Konvertierungsspezifizierer zu akzeptieren (und zu ignorieren).
Template-Renderer für POSIX-Shell-Befehle
Sowohl als praktische Demonstration der Vorteile der Unterstützung für verzögertes Rendering als auch als eigenständiges wertvolles Feature wird dem Modul shlex ein neuer sh Template-Renderer hinzugefügt. Dieser Renderer erzeugt Strings, bei denen alle interpolierten Felder mit shlex.quote() escaped werden.
Die subprocess.Popen API (und darauf aufbauende höherstufige APIs wie subprocess.run()) werden aktualisiert, um Interpolationstemplates zu akzeptieren und sie gemäß dem neuen shlex.sh Renderer zu behandeln.
Hintergrund
Dieser PEP wurde ursprünglich als Konkurrent zu PEP 498 vorgeschlagen. Nachdem klar wurde, dass der Vorschlag für eifrige Renderung deutlich mehr unmittelbare Unterstützung hatte, war er mehrere Jahre lang in einem aufgeschobenen Zustand, bis weitere Erfahrungen mit dem einfacheren Ansatz von PEP 498 gesammelt wurden, der nur eifrige Renderung ohne die zusätzliche Komplexität der Unterstützung für verzögerte Renderung bot.
Seitdem sind f-Strings sehr beliebt geworden, und PEP 701 wurde eingeführt, um einige Kanten und Einschränkungen in ihrer Syntax und Semantik zu beheben. Der Vorschlag für Template-Literale wurde 2023 aktualisiert, um den aktuellen Kenntnisstand über f-Strings und die Verbesserungen aus PEP 701 widerzuspiegeln.
Im Jahr 2024 wurde PEP 750 veröffentlicht, der einen allgemeinen Mechanismus für benutzerdefinierte getaggte String-Präfixe vorschlägt, anstatt des engeren Template-Literal-Vorschlags in diesem PEP. Dieser PEP wurde erneut aktualisiert, sowohl um neue Ideen aufzunehmen, die vom getaggten String-Vorschlag inspiriert wurden, als auch um die wahrgenommenen Vorteile des engeren Template-Literal-Syntax-Vorschlags in diesem PEP gegenüber dem allgemeineren getaggten String-Vorschlag zu beschreiben.
Zusammenfassung der Unterschiede zu f-Strings
Die Hauptunterschiede zwischen f-Strings und t-Strings sind:
- der Präfix
t(Template-Literal) bedeutet verzögerte Renderung, verwendet aber ansonsten weitgehend die gleiche Syntax und Semantik wie formatierte Strings - Template-Literale sind zur Laufzeit als neue Art von Objekten (
types.TemplateLiteral) verfügbar - die Standard-Renderung, die von formatierten Strings verwendet wird, wird auf einem Template-Literal-Objekt durch Aufruf von
format(template)aufgerufen, anstatt implizit im kompilierten Code zu erfolgen - im Gegensatz zu f-Strings (bei denen Konvertierungsspezifizierer direkt im Compiler behandelt werden) werden t-String-Konvertierungsspezifizierer zur Render-Zeit von der Rendering-Funktion behandelt
- der neue Konvertierungsspezifizierer
!()zeigt an, dass der Feld-Ausdruck ein aufrufbares Objekt ist, das beim Verwenden der Standard-Rendering-Funktionformat()aufgerufen werden soll. Dieser Spezifizierer wird ausdrücklich *nicht* zu f-Strings hinzugefügt (da er dort sinnlos ist). - ein zweites
!ist in t-String-Konvertierungsspezifizierern erlaubt (wobei nachfolgender Text ignoriert wird), um benutzerdefinierten Template-Rendering-Funktionen zu erlauben, benutzerdefinierte Konvertierungsspezifizierer zu akzeptieren, ohne die Standard-Rendering-MethodeTemplateLiteral.render()zu beeinträchtigen. Dieses Feature wird ausdrücklich *nicht* zu f-Strings hinzugefügt (da es dort sinnlos ist). - während
f"Message {here}"von f-Strings *semantisch* äquivalent zuformat(t"Message {here}")wäre, werden f-Strings weiterhin direkt im Compiler unterstützt und vermeiden daher den Laufzeit-Overhead der tatsächlichen Nutzung der verzögerten Rendering-Maschinerie, die für t-Strings erforderlich ist
Zusammenfassung der Unterschiede zu getaggten Strings
Als getaggte Strings erstmals vorgeschlagen wurden, gab es mehrere bemerkenswerte Unterschiede zum Vorschlag in PEP 501, abgesehen vom Oberflächen-Syntaxunterschied, ob Renderfunktionsaufrufe als render(t"template literal") oder als render"template literal" geschrieben werden.
Im Laufe der ursprünglichen Diskussion zu PEP 750 wurden viele dieser Unterschiede eliminiert, entweder indem PEP 501 diesen Aspekt des Vorschlags von PEP 750 übernahm (wie das verzögerte Anwenden von Konvertierungsspezifizierern) oder indem PEP 750 geändert wurde, um einen Aspekt des Vorschlags von PEP 501 beizubehalten (wie die Definition eines dedizierten Typs zur Aufnahme von Template-Segmenten anstatt deren Darstellung als einfache Sequenzen).
Der Hauptunterschied, der verbleibt, ist, dass dieser PEP argumentiert, dass die Hinzufügung *nur* des t-String-Präfixes eine ausreichende Verbesserung darstellt, um alle in PEP 750 beschriebenen gewünschten Vorteile zu erzielen. Die Erweiterung zu einer generalisierten „getaggten String“-Syntax ist nicht notwendig und verursacht zusätzliche Probleme, die vermieden werden können.
Die beiden PEPs unterscheiden sich auch in ihren vorgeschlagenen Ansätzen zur Handhabung der verzögerten Auswertung von Template-Feldern.
Obwohl es *andere* Unterschiede zwischen den beiden Vorschlägen gibt, sind diese Unterschiede eher kosmetischer als substanzieller Natur. Insbesondere:
- Dieser PEP schlägt unterschiedliche Namen für die Protokolle des strukturellen Tippens vor
- Dieser PEP schlägt spezifische Namen für die konkreten Implementierungstypen vor
- Dieser PEP schlägt genaue Details für die vorgeschlagenen APIs der konkreten Implementierungstypen vor (einschließlich Unterstützung für Verkettung und Wiederholung, die nicht Teil der Protokolle des strukturellen Tippens sind)
- Dieser PEP schlägt Änderungen am bestehenden Built-in
format()vor, um es direkt als Template-Feld-Renderer nutzbar zu machen
Die beiden PEPs unterscheiden sich auch darin, *wie* sie ihre Argumente für die Unterstützung verzögerter Renderung darlegen. Dieser PEP konzentriert sich stärker auf das konkrete Implementierungskonzept der Verwendung von Template-Literalen, um die Schritte „Interpolation“ und „Rendering“ bei der f-String-Verarbeitung zeitlich zu trennen und dies dann zu nutzen, um die potenziellen Code-Injection-Risiken zu reduzieren, die mit dem Missbrauch von f-Strings verbunden sind. PEP 750 konzentriert sich stärker darauf, wie native Templating-Unterstützung Verhaltensweisen ermöglicht, die über vorhandene stringbasierte Templating-Methoden schwer oder unmöglich zu erreichen sind. Wie bei den oben genannten kosmetischen Unterschieden handelt es sich hierbei eher um einen Stilunterschied als um einen Substanzunterschied.
Begründung
f-Strings (PEP 498) vereinfachten das Interpolieren von Werten in Strings mit vollem Zugriff auf die lexikalischen Namespace-Semantiken von Python, aber zu dem Preis, dass das Interpolieren von Werten in sensible Ziele wie SQL-Abfragen, Shell-Befehle und HTML-Vorlagen eine viel sauberere Syntax aufweisen, wenn sie ohne Rücksicht auf Code-Injection-Angriffe behandelt werden, als wenn sie korrekt behandelt werden.
Dieser PEP schlägt vor, die Option zu bieten, das tatsächliche Rendering eines Template-Literals in eine formatierte Zeichenfolge auf seine __format__ Methode zu verzögern, was die Verwendung anderer Template-Renderer durch Übergabe des Templates als First-Class-Objekt ermöglicht.
Obwohl die technischen Details sehr unterschiedlich sind, ist die in diesem PEP vorgeschlagene Schnittstelle types.TemplateLiteral konzeptionell der FormattableString-Typen, der der nativen Interpolationsunterstützung in C# 6.0 zugrunde liegt, sowie den JavaScript-Template-Literalen, die in ES6 eingeführt wurden.
Obwohl es nicht die ursprüngliche Motivation für die Entwicklung des Vorschlags war, gelten viele der in PEP 750 beschriebenen Vorteile für die Definition domänenspezifischer Sprachen auch für diesen PEP (einschließlich des Potenzials für DSL-spezifische semantische Hervorhebung in Code-Editoren basierend auf den Typspezifikationen deklarierter Template-Variablen und Rendering-Funktionsparametern).
Spezifikation
Dieser PEP schlägt einen neuen t-String-Präfix vor, der zur Erzeugung einer Instanz eines neuen Typs, types.TemplateLiteral, führt.
Template-Literale sind Unicode-Strings (Byte-Literale sind nicht erlaubt), und die Verkettung von String-Literalen funktioniert wie gewohnt, wobei das gesamte kombinierte Literal das Template-Literal bildet.
Der Template-String wird wie für f-Strings in PEP 498 und PEP 701 beschrieben in Literale, Ausdrücke, Format-Spezifizierer und Konvertierungs-Spezifizierer zerlegt. Die Syntax für Konvertierungs-Spezifizierer wird so angepasst, dass beliebige Strings akzeptiert werden (ausgenommen solche, die {, } oder : enthalten), anstatt auf gültige Python-Identifikatoren beschränkt zu sein.
Anstatt jedoch direkt in einen formatierten String gerendert zu werden, werden diese Komponenten stattdessen in Instanzen neuer Typen mit folgendem Verhalten organisiert:
class TemplateLiteralText(str):
# This is a renamed and extended version of the DecodedConcrete type in PEP 750
# Real type would be implemented in C, this is an API compatible Python equivalent
_raw: str
def __new__(cls, raw: str):
decoded = raw.encode("utf-8").decode("unicode-escape")
if decoded == raw:
decoded = raw
text = super().__new__(cls, decoded)
text._raw = raw
return text
@staticmethod
def merge(text_segments:Sequence[TemplateLiteralText]) -> TemplateLiteralText:
if len(text_segments) == 1:
return text_segments[0]
return TemplateLiteralText("".join(t._raw for t in text_segments))
@property
def raw(self) -> str:
return self._raw
def __repr__(self) -> str:
return f"{type(self).__name__}(r{self._raw!r})"
def __add__(self, other:Any) -> TemplateLiteralText|NotImplemented:
if isinstance(other, TemplateLiteralText):
return TemplateLiteralText(self._raw + other._raw)
return NotImplemented
def __mul__(self, other:Any) -> TemplateLiteralText|NotImplemented:
try:
factor = operator.index(other)
except TypeError:
return NotImplemented
return TemplateLiteralText(self._raw * factor)
__rmul__ = __mul__
class TemplateLiteralField(NamedTuple):
# This is mostly a renamed version of the InterpolationConcrete type in PEP 750
# However:
# - value is eagerly evaluated (values were all originally lazy in PEP 750)
# - conversion specifiers are allowed to be arbitrary strings
# - order of fields is adjusted so the text form is the first field and the
# remaining parameters match the updated signature of the `*format` builtin
# Real type would be implemented in C, this is an API compatible Python equivalent
expr: str
value: Any
format_spec: str | None = None
conversion_spec: str | None = None
def __repr__(self) -> str:
return (f"{type(self).__name__}({self.expr}, {self.value!r}, "
f"{self.format_spec!r}, {self.conversion_spec!r})")
def __str__(self) -> str:
return format(self.value, self.format_spec, self.conversion_spec)
def __format__(self, format_override) -> str:
if format_override:
format_spec = format_override
else:
format_spec = self.format_spec
return format(self.value, format_spec, self.conversion_spec)
class TemplateLiteral:
# This type corresponds to the TemplateConcrete type in PEP 750
# Real type would be implemented in C, this is an API compatible Python equivalent
_raw_template: str
_segments = tuple[TemplateLiteralText|TemplateLiteralField]
def __new__(cls, raw_template:str, *segments:TemplateLiteralText|TemplateLiteralField):
self = super().__new__(cls)
self._raw_template = raw_template
# Check if there are any adjacent text segments that need merging
# or any empty text segments that need discarding
type_err = "Template literal segments must be template literal text or field instances"
text_expected = True
needs_merge = False
for segment in segments:
match segment:
case TemplateLiteralText():
if not text_expected or not segment:
needs_merge = True
break
text_expected = False
case TemplateLiteralField():
text_expected = True
case _:
raise TypeError(type_err)
if not needs_merge:
# Match loop above will have checked all segments
self._segments = segments
return self
# Merge consecutive runs of text fields and drop any empty text fields
merged_segments:list[TemplateLiteralText|TemplateLiteralField] = []
pending_merge:list[TemplateLiteralText] = []
for segment in segments:
match segment:
case TemplateLiteralText() as text_segment:
if text_segment:
pending_merge.append(text_segment)
case TemplateLiteralField():
if pending_merge:
merged_segments.append(TemplateLiteralText.merge(pending_merge))
pending_merge.clear()
merged_segments.append(segment)
case _:
# First loop above may not check all segments when a merge is needed
raise TypeError(type_err)
if pending_merge:
merged_segments.append(TemplateLiteralText.merge(pending_merge))
pending_merge.clear()
self._segments = tuple(merged_segments)
return self
@property
def raw_template(self) -> str:
return self._raw_template
@property
def segments(self) -> tuple[TemplateLiteralText|TemplateLiteralField]:
return self._segments
def __len__(self) -> int:
return len(self._segments)
def __iter__(self) -> Iterable[TemplateLiteralText|TemplateLiteralField]:
return iter(self._segments)
# Note: template literals do NOT define any relative ordering
def __eq__(self, other):
if not isinstance(other, TemplateLiteral):
return NotImplemented
return (
self._raw_template == other._raw_template
and self._segments == other._segments
and self.field_values == other.field_values
and self.format_specifiers == other.format_specifiers
)
def __repr__(self) -> str:
return (f"{type(self).__name__}(r{self._raw!r}, "
f"{', '.join(map(repr, self._segments))})")
def __format__(self, format_specifier) -> str:
# When formatted, render to a string, and then use string formatting
return format(self.render(), format_specifier)
def render(self, *, render_template=''.join, render_text=str, render_field=format):
... # See definition of the template rendering semantics below
def __add__(self, other) -> TemplateLiteral|NotImplemented:
if isinstance(other, TemplateLiteral):
combined_raw_text = self._raw + other._raw
combined_segments = self._segments + other._segments
return TemplateLiteral(combined_raw_text, *combined_segments)
if isinstance(other, str):
# Treat the given string as a new raw text segment
combined_raw_text = self._raw + other
combined_segments = self._segments + (TemplateLiteralText(other),)
return TemplateLiteral(combined_raw_text, *combined_segments)
return NotImplemented
def __radd__(self, other) -> TemplateLiteral|NotImplemented:
if isinstance(other, str):
# Treat the given string as a new raw text segment. This effectively
# has precedence over string concatenation in CPython due to
# https://github.com/python/cpython/issues/55686
combined_raw_text = other + self._raw
combined_segments = (TemplateLiteralText(other),) + self._segments
return TemplateLiteral(combined_raw_text, *combined_segments)
return NotImplemented
def __mul__(self, other) -> TemplateLiteral|NotImplemented:
try:
factor = operator.index(other)
except TypeError:
return NotImplemented
if not self or factor == 1:
return self
if factor < 1:
return TemplateLiteral("")
repeated_text = self._raw_template * factor
repeated_segments = self._segments * factor
return TemplateLiteral(repeated_text, *repeated_segments)
__rmul__ = __mul__
(Hinweis: Dies ist ein illustratives Beispiel für die Implementierung. Die genaue Methode zur Compile-Zeit-Konstruktion und die internen Details der Datenverwaltung von types.TemplateLiteral gelten als Implementierungsdetail, das nicht vom PEP spezifiziert wird. Das erwartete Verhalten nach der Konstruktion der öffentlichen APIs von types.TemplateLiteral-Instanzen wird jedoch durch den obigen Code spezifiziert, ebenso wie die Konstruktorsignatur zum Erstellen von Template-Instanzen zur Laufzeit).
Das Ergebnis eines Template-Literal-Ausdrucks ist eine Instanz dieses Typs und kein bereits gerenderter String. Das Rendering erfolgt erst, wenn die render Methode der Instanz aufgerufen wird (entweder direkt oder indirekt über __format__).
Der Compiler übergibt die folgenden Details zur späteren Verwendung an das Template-Literal:
- einen String, der das rohe Template wie im Quellcode geschrieben enthält
- eine Sequenz von Template-Segmenten, wobei jedes Segment entweder
- ein Literal-Textsegment ist (ein regulärer Python-String, der auch Zugriff auf seine rohe Form bietet)
- ein geparisiertes Template-Interpolationsfeld, das den Text des interpolierten Ausdrucks (als regulärer String), sein ausgewertetes Ergebnis, den Format-Spezifizierer-Text (wobei alle Substitutionsfelder als f-String eifrig ausgewertet werden) und den Konvertierungs-Spezifizierer-Text (als regulärer String) angibt
Das rohe Template ist einfach das Template-Literal als String. Standardmäßig wird es verwendet, um eine lesbare Darstellung des Template-Literals bereitzustellen, aber Template-Renderer können es auch für andere Zwecke verwenden (z. B. als Schlüssel für eine Cache-Suche).
Die geparste Template-Struktur stammt aus PEP 750 und besteht aus einer Sequenz von Template-Segmenten, die den Textsegmenten und Interpolationsfeldern im Template-String entsprechen.
Dieser Ansatz ist darauf ausgelegt, es dem Compiler zu ermöglichen, jedes Segment des Templates der Reihe nach vollständig zu verarbeiten, bevor schließlich Code ausgegeben wird, um alle Template-Segmente an den Template-Literal-Konstruktor zu übergeben.
Zum Beispiel, unter der Annahme der folgenden Laufzeitwerte
names = ["Alice", "Bob", "Carol", "Eve"]
field_width = 10
def expressions():
return 42
Das Template aus dem Vorschlagsabschnitt würde zur Laufzeit wie folgt dargestellt werden:
TemplateLiteral(
r"Substitute {names:>{field_width}} and {expressions()!r} at runtime",
TemplateLiteralText(r"Substitute "),
TemplateLiteralField("names", ["Alice", "Bob", "Carol", "Eve"], ">10", ""),
TemplateLiteralText(r" and "),
TemplateLiteralField("expressions()", 42, "", "r"),
)
Rendering von Templates
Die Implementierung von TemplateLiteral.render definiert den Rendering-Prozess in Form der folgenden Renderer:
- eine übergeordnete
render_templateOperation, die definiert, wie die Sequenz aus gerenderten Text- und Feldsegmenten zu einem vollständig gerenderten Ergebnis zusammengesetzt wird. Der Standard-Template-Renderer ist die String-Verkettung mittels''.join. - eine pro Textsegment
render_textOperation, die die einzelnen Literal-Textsegmente innerhalb des Templates empfängt. Der Standard-Textrenderer ist der eingebautestrKonstruktor. - eine pro Feldsegment
render_fieldOperation, die den Feldwert, den Format-Spezifizierer und den Konvertierungs-Spezifizierer für Substitutionsfelder innerhalb des Templates empfängt. Der Standard-Feldrenderer ist der eingebauteformat().
Gegeben die obige geparste Template-Darstellung, wären die Semantiken des Template-Renderings äquivalent zu folgendem:
def render(self, *, render_template=''.join, render_text=str, render_field=format):
rendered_segments = []
for segment in self._segments:
match segment:
case TemplateLiteralText() as text_segment:
rendered_segments.append(render_text(text_segment))
case TemplateLiteralField() as field_segment:
rendered_segments.append(render_field(*field_segment[1:]))
return render_template(rendered_segments)
Format-Spezifizierer
Die Syntax und Verarbeitung von Feld-Spezifizierern in t-Strings ist so definiert, dass sie der von f-Strings entspricht.
Dies schließt ein, dass Feld-Spezifizierer selbst f-String-Substitutionsfelder enthalten können. Der rohe Text der Feld-Spezifizierer (ohne Verarbeitung von Substitutionsfeldern) bleibt als Teil des vollständigen rohen Template-Strings erhalten.
Die geparisierten Feld-Spezifizierer empfangen den Feld-Spezifizierer-String mit bereits aufgelösten Substitutionen. Das Präfix : wird ebenfalls weggelassen.
Abgesehen davon, dass sie während des Parsens vom Substitutionsausdruck getrennt werden, werden Format-Spezifizierer vom Interpolationstemplatparser ansonsten als undurchsichtige Strings behandelt – die Zuweisung von Semantik zu diesen (oder alternativ deren Verbot) wird zur Rendering-Zeit vom Feld-Renderer gehandhabt.
Konvertierungsspezifizierer
Zusätzlich zur bestehenden Unterstützung für die Konvertierungsspezifizierer a, r und s werden str.format() und str.format_map() aktualisiert, um () als Konvertierungsspezifizierer zu akzeptieren, der „rufe den interpolierten Wert auf“ bedeutet.
Wo PEP 701 Konvertierungsspezifizierer auf NAME-Token beschränkt, wird dieser PEP stattdessen FSTRING_MIDDLE-Tokens erlauben (so dass nur {, } und : disallowed sind). Diese Änderung wird hauptsächlich vorgenommen, um die verzögerte Feld-Renderung mit dem Konvertierungsspezifizierer !() zu unterstützen, erlaubt aber auch benutzerdefinierten Rendering-Funktionen mehr Flexibilität bei der Definition eigener Konvertierungsspezifizierer im Vergleich zu denen, die für den Standard-Feld-Renderer von format() definiert sind.
Konvertierungsspezifizierer werden immer noch als einfache Strings behandelt und unterstützen KEINE Substitution von Feldern.
Die geparisierten Konvertierungsspezifizierer empfangen den Konvertierungsspezifizierer-String mit weggelassenem ! Präfix.
Um benutzerdefinierten Vorlagenrenderern zu ermöglichen, ihre eigenen benutzerdefinierten Konvertierungsspezifizierer zu definieren, ohne dass der Standard-Renderer fehlschlägt, werden Konvertierungsspezifizierer nun eine benutzerdefinierte Suffixkomponente enthalten, der ein zweites !-Zeichen vorangestellt ist. Das bedeutet, dass !!<custom>, !a!<custom>, !r!<custom>, !s!<custom> und !()!<custom> alle gültige Konvertierungsspezifizierer in einem Vorlagenliteral wären.
Wie oben beschrieben, unterstützt das Standard-Rendering die ursprünglichen !a, !r und !s Konvertierungsspezifizierer, die in PEP 3101 definiert sind, zusammen mit dem neuen !() Lazy-Feld-Evaluierungs-Konvertierungsspezifizierer, der in dieser PEP definiert ist. Das Standard-Rendering ignoriert benutzerdefinierte Konvertierungsspezifizierer-Suffixe.
Die vollständige Zuordnung zwischen den Standard-Konvertierungsspezifizierern und den Spezialmethoden, die auf dem interpolierten Wert aufgerufen werden, wenn das Feld gerendert wird
- Keine Konvertierung (leerer String):
__format__(mit Format-Spezifizierer als Parameter) a:__repr__(gemäß der eingebauten Funktionascii())r:__repr__(gemäß der eingebauten Funktionrepr())s:__str__(gemäß der eingebauten Funktionstr)():__call__(ohne Parameter)
Wenn eine Konvertierung stattfindet, wird __format__ (mit dem Format-Spezifizierer) auf dem Ergebnis der Konvertierung aufgerufen, anstatt auf dem ursprünglichen Objekt.
Die Änderungen an format() und die Ergänzung von operator.convert_field() erleichtern es benutzerdefinierten Renderern, auch die Standard-Konvertierungsspezifizierer zu unterstützen.
f-Strings selbst werden den neuen !()-Konvertierungsspezifizierer NICHT unterstützen (da er redundant ist, wenn Wertinterpolation und Wert-Rendering gleichzeitig stattfinden). Sie werden auch NICHT die Verwendung benutzerdefinierter Konvertierungsspezifizierer unterstützen (da die Rendering-Funktion zur Kompilierzeit bekannt ist und die benutzerdefinierten Spezifizierer nicht verwendet werden).
Neue Feldkonvertierungs-API im operator-Modul
Um die Anwendung der Standard-Konvertierungsspezifizierer in benutzerdefinierten Vorlagen-Rendering-Funktionen zu unterstützen, wird eine neue Funktion operator.convert_field() hinzugefügt
def convert_field(value, conversion_spec=''):
"""Apply the given string formatting conversion specifier to the given value"""
std_spec, sep, custom_spec = conversion_spec.partition("!")
match std_spec:
case '':
return value
case 'a':
return ascii(value)
case 'r':
return repr(value)
case 's':
return str(value)
case '()':
return value()
if not sep:
err = f"Invalid conversion specifier {std_spec!r}"
else:
err = f"Invalid conversion specifier {std_spec!r} in {conversion_spec!r}"
raise ValueError(f"{err}: expected '', 'a', 'r', 's' or '()')
Konvertierungsspezifizierer-Parameter zu format() hinzugefügt
Die Signatur und das Verhalten der eingebauten Funktion format() werden aktualisiert
def format(value, format_spec='', conversion_spec=''):
if conversion_spec:
value_to_format = operator.convert_field(value)
else:
value_to_format = value
return type(value_to_format).__format__(value, format_spec)
Wenn ein nicht-leerer Konvertierungsspezifizierer angegeben wird, wird der Wert mit operator.convert_field() konvertiert, bevor die Methode __format__ aufgerufen wird.
Die Signatur der Spezialmethode __format__ ändert sich NICHT (nur Format-Spezifizierer werden vom zu formatierenden Objekt behandelt).
Strukturelles Tippen und Duck-Typing
Um benutzerdefinierten Renderern die Akzeptanz alternativer Vorlagenimplementierungen zu ermöglichen (anstatt eng an die nativen Vorlagenliteral-Typen gebunden zu sein), werden die folgenden strukturellen Protokolle dem Modul typing hinzugefügt
@runtime_checkable
class TemplateText(Protocol):
# Renamed version of PEP 750's Decoded protocol
def __str__(self) -> str:
...
raw: str
@runtime_checkable
class TemplateField(Protocol):
# Renamed and modified version of PEP 750's Interpolation protocol
def __len__(self):
...
def __getitem__(self, index: int):
...
def __str__(self) -> str:
...
expr: str
value: Any
format_spec: str | None = None
conversion_spec: str | None = None
@runtime_checkable
class InterpolationTemplate(Protocol):
# Corresponds to PEP 750's Template protocol
def __iter__(self) -> Iterable[TemplateText|TemplateField]:
...
raw_template: str
Beachten Sie, dass die strukturellen Protokoll-APIs erheblich schmaler sind als die vollständigen Implementierungs-APIs für TemplateLiteralText, TemplateLiteralField und TemplateLiteral.
Code, der Interpolationsvorlagen akzeptieren und spezifische Handhabung dafür definieren möchte, ohne eine Abhängigkeit vom Modul typing einzuführen oder den Code auf die Handhabung konkreter Vorlagenliteral-Typen zu beschränken, sollte stattdessen eine Prüfung auf Attributexistenz auf raw_template durchführen.
Schreiben benutzerdefinierter Renderer
Das Schreiben eines benutzerdefinierten Renderers erfordert keine spezielle Syntax. Stattdessen sind benutzerdefinierte Renderer gewöhnliche aufrufbare Objekte, die eine Interpolationsvorlage direkt verarbeiten, entweder durch Aufrufen der Methode render() mit alternativen Implementierungen für render_template, render_text und/oder render_field oder durch direkten Zugriff auf die Datenattribute der Vorlage.
Zum Beispiel würde die folgende Funktion eine Vorlage rendern, indem sie die repr-Implementierungen von Objekten anstelle ihrer nativen Formatierungsunterstützung verwendet
def repr_format(template):
def render_field(value, format_spec, conversion_spec):
converted_value = operator.convert_field(value, conversion_spec)
return format(repr(converted_value), format_spec)
return template.render(render_field=render_field)
Der dargestellte benutzerdefinierte Renderer berücksichtigt die Konvertierungsspezifizierer in der ursprünglichen Vorlage, aber es ist auch möglich, sie zu ignorieren und die interpolierten Werte direkt zu rendern
def input_repr_format(template):
def render_field(value, format_spec, __):
return format(repr(value), format_spec)
return template.render(render_field=render_field)
Beim Schreiben benutzerdefinierter Renderer ist zu beachten, dass der Rückgabetyp des gesamten Rendering-Vorgangs durch den Rückgabetyp des übergebenen render_template-Aufrufs bestimmt wird. Während dies für Formatierungszwecke immer noch ein String sein wird, ist die Erzeugung von Nicht-String-Objekten *erlaubt*. Zum Beispiel könnte ein benutzerdefinierter SQL-Vorlagen-Renderer einen Aufruf von sqlalchemy.sql.text beinhalten, der ein SQL Alchemy Query-Objekt erzeugt. Ein Vorlagen-Renderer, der mit Subprozessaufrufen zu tun hat, könnte eine Zeichenkettenfolge erzeugen, die für die Übergabe an subprocess.run geeignet ist, oder er könnte sogar subprocess.run direkt aufrufen und das Ergebnis zurückgeben.
Auch Nicht-Strings können von render_text und render_field zurückgegeben werden, solange sie mit einer render_template-Implementierung gepaart sind, die dieses Verhalten erwartet.
Benutzerdefinierte Renderer, die den in PEP 750 beschriebenen Mustervergleichsstil verwenden, werden ebenfalls unterstützt
# Use the structural typing protocols rather than the concrete implementation types
from typing import InterpolationTemplate, TemplateText, TemplateField
def greet(template: InterpolationTemplate) -> str:
"""Render an interpolation template using structural pattern matching."""
result = []
for segment in template:
match segment:
match segment:
case TemplateText() as text_segment:
result.append(text_segment)
case TemplateField() as field_segment:
result.append(str(field_segment).upper())
return f"{''.join(result)}!"
Auswertung von Ausdrücken
Wie bei f-Strings werden die Unterausdrücke, die aus der Interpolationsvorlage extrahiert werden, im Kontext ausgewertet, in dem das Vorlagenliteral erscheint. Das bedeutet, dass der Ausdruck vollständigen Zugriff auf lokale, nicht-lokale und globale Variablen hat. Jede gültige Python-Ausdruck kann innerhalb von {} verwendet werden, einschließlich Funktions- und Methodenaufrufen.
Da die Substitutionsausdrücke dort ausgewertet werden, wo der String im Quellcode erscheint, gibt es keine zusätzlichen Sicherheitsbedenken hinsichtlich des Inhalts des Ausdrucks selbst, da man auch einfach denselben Ausdruck geschrieben und die Laufzeitfeldanalyse verwendet hätte.
>>> bar=10
>>> def foo(data):
... return data + 20
...
>>> str(t'input={bar}, output={foo(bar)}')
'input=10, output=30'
Ist im Wesentlichen gleichwertig mit
>>> 'input={}, output={}'.format(bar, foo(bar))
'input=10, output=30'
Umgang mit Code-Injection-Angriffen
Die PEP 498 formatierten String-Syntax macht es attraktiv, Code wie den folgenden zu schreiben
runquery(f"SELECT {column} FROM {table};")
runcommand(f"cat {filename}")
return_response(f"<html><body>{response.body}</body></html>")
Dies sind alles potenzielle Vektoren für Code-Injektionsangriffe, wenn eine der interpolierten Variablen aus einer nicht vertrauenswürdigen Quelle stammt. Der spezifische Vorschlag in dieser PEP soll es einfach machen, anwendungsfallspezifische Renderer zu schreiben, die interpolierte Werte für den relevanten Sicherheitskontext entsprechend quotieren.
runquery(sql(t"SELECT {column} FROM {table} WHERE column={value};"))
runcommand(sh(t"cat {filename}"))
return_response(html(t"<html><body>{response.body}</body></html>"))
Diese PEP deckt nicht die sofortige Aufnahme aller solchen Renderer in die Standardbibliothek ab (obwohl einer für Shell-Escaping vorgeschlagen wird), sondern schlägt vor, sicherzustellen, dass sie leicht von Drittanbieterbibliotheken bereitgestellt werden können und möglicherweise zu einem späteren Zeitpunkt in die Standardbibliothek aufgenommen werden können.
Im Laufe der Zeit wird erwartet, dass APIs, die potenziell gefährliche String-Eingaben verarbeiten, aktualisiert werden, um Interpolationsvorlagen nativ zu akzeptieren, was es ermöglicht, problematische Codebeispiele einfach durch Ersetzen des f-String-Präfixes durch ein t zu beheben.
runquery(t"SELECT {column} FROM {table};")
runcommand(t"cat {filename}")
return_response(t"<html><body>{response.body}</body></html>")
Es wird vorgeschlagen, einen Renderer im Modul shlex aufzunehmen, der ein POSIX-Shell-ähnlicheres Erlebnis für den Zugriff auf externe Programme bietet, ohne die erheblichen Risiken, die mit der Ausführung von os.system oder der Aktivierung der System-Shell bei Verwendung der subprocess-Modul-APIs verbunden sind. Dieser Renderer bietet eine Schnittstelle zum Ausführen externer Programme, die von der des Julia-Programmiersprache inspiriert ist, nur mit der Backtick-basierten \`cat $filename\`-Syntax, die durch t"cat {filename}"-artige Vorlagenliterale ersetzt wird. Siehe mehr im Abschnitt Renderer für Shell-Escaping zu shlex hinzugefügt.
Fehlerbehandlung
Entweder Kompilierungszeit- oder Laufzeitfehler können bei der Verarbeitung von Interpolationsausdrücken auftreten. Kompilierungszeitfehler beschränken sich auf Fehler, die beim Parsen einer Vorlagenzeichenkette in ihre Bestandteile erkannt werden können. Diese Fehler lösen alle SyntaxError aus.
Unzureichende Klammern
>>> t'x={x'
File "<stdin>", line 1
t'x={x'
^
SyntaxError: missing '}' in template literal expression
Ungültige Ausdrücke
>>> t'x={!x}'
File "<fstring>", line 1
!x
^
SyntaxError: invalid syntax
Laufzeitfehler treten bei der Auswertung der Ausdrücke innerhalb einer Vorlagenzeichenkette auf, bevor das Vorlagenliteral-Objekt erstellt wird. Siehe PEP 498 für einige Beispiele.
Unterschiedliche Renderer können auch zusätzliche Laufzeitbeschränkungen für akzeptable interpolierte Ausdrücke und andere Formatierungsdetails auferlegen, die als Laufzeitausnahmen gemeldet werden.
Renderer für Shell-Escaping zu shlex hinzugefügt
Als Referenzimplementierung kann ein Renderer für sicheres POSIX-Shell-Escaping dem Modul shlex hinzugefügt werden. Dieser Renderer würde sh genannt werden und wäre gleichwertig mit dem Aufruf von shlex.quote für jeden Feldwert im Vorlagenliteral.
Somit
os.system(shlex.sh(t'cat {myfile}'))
hätte das gleiche Verhalten wie
os.system('cat ' + shlex.quote(myfile)))
Die Implementierung wäre
def sh(template: TemplateLiteral):
def render_field(value, format_spec, conversion_spec)
field_text = format(value, format_spec, conversion_spec)
return quote(field_text)
return template.render(render_field=render_field)
Die Hinzufügung von shlex.sh wird die bestehenden Warnungen in der Dokumentation des Moduls subprocess, dass die Übergabe von shell=True am besten vermieden werden sollte, und auch den Verweis aus der Dokumentation von auf die höherstufigen os.system()subprocess-APIs NICHT ändern.
Änderungen am subprocess-Modul
Mit dem zusätzlichen Renderer im shlex-Modul und der Einführung von Vorlagenliteralen könnte das Modul subprocess so geändert werden, dass es Vorlagenliterale als zusätzlichen Eingabetyp für Popen akzeptiert, so wie es bereits eine Sequenz oder einen String mit unterschiedlichem Verhalten für jeden akzeptiert.
Mit der Einführung von Vorlagenliteralen könnte subprocess.Popen (und somit alle seine höherstufigen Funktionen wie subprocess.run()) Strings auf sichere Weise akzeptieren (zumindest auf POSIX-Systemen).
Zum Beispiel:
subprocess.run(t'cat {myfile}', shell=True)
würde automatisch den in dieser PEP bereitgestellten Renderer shlex.sh verwenden. Daher wäre die Verwendung von shlex innerhalb eines Aufrufs von subprocess.run wie folgt
subprocess.run(shlex.sh(t'cat {myfile}'), shell=True)
redundant, da run jegliche Vorlagenliterale automatisch über shlex.sh rendern würde
Alternativ könnte subprocess.Popen, wenn es ohne shell=True ausgeführt wird, immer noch eine ergonomischere Syntax für Subprozesse bereitstellen. Zum Beispiel
subprocess.run(t'cat {myfile} --flag {value}')
wäre gleichwertig mit
subprocess.run(['cat', myfile, '--flag', value])
oder genauer gesagt
subprocess.run(shlex.split(f'cat {shlex.quote(myfile)} --flag {shlex.quote(value)}'))
Dies würde geschehen, indem zuerst der Renderer shlex.sh wie oben verwendet wird, und dann shlex.split auf das Ergebnis angewendet wird.
Die Implementierung innerhalb von subprocess.Popen._execute_child würde wie folgt aussehen
if hasattr(args, "raw_template"):
import shlex
if shell:
args = [shlex.sh(args)]
else:
args = shlex.split(shlex.sh(args))
Wie man das lehrt
Diese PEP enthält bewusst zwei Standard-Renderer, die in Lehrmaterialien immer verfügbar sein werden: die eingebaute Funktion format() und den neuen POSIX-Shell-Renderer shlex.sh.
Zusammen können diese beiden Renderer verwendet werden, um ein grundlegendes Verständnis von verzögertem Rendering aufzubauen, das auf der anfänglichen Einführung von f-Strings in die String-Formatierung basiert. Dieses anfängliche Verständnis hätte zum Ziel, Studenten die effektive *Nutzung* von Vorlagenliteralen in Kombination mit bereits vorhandenen Vorlagen-Rendering-Funktionen zu ermöglichen.
Zum Beispiel könnten f"{'some text'}", f"{value}", f"{value!r}", , f"{callable()}" alle eingeführt werden.
Diese Operationen könnten dann als format(t"{'some text'}"), format(t"{value}"), format(t"{value!r}"), , format(t"{callable()}") umgeschrieben werden, um die Beziehung zwischen der sofortigen Rendering-Form und der verzögerten Rendering-Form zu veranschaulichen.
Der Unterschied zwischen "Zeitpunkt der Vorlagendefinition" (oder "Zeitpunkt der Interpolation") und "Zeitpunkt des Vorlagen-Renderings" kann dann weiter untersucht werden, indem die Vorlagenliterale als lokale Variablen gespeichert und ihre Darstellungen getrennt von den Ergebnissen der format-Aufrufe betrachtet werden. Zu diesem Zeitpunkt kann die Syntax t"{callable!()}" eingeführt werden, um zwischen Feld-Ausdrücken zu unterscheiden, die zum Zeitpunkt der Vorlagendefinition aufgerufen werden, und denen, die zum Zeitpunkt des Vorlagen-Renderings aufgerufen werden.
Schließlich können die Unterschiede zwischen den Ergebnissen von f"{'some text'}", format(t"{'some text'}") und shlex.sh(t"{'some text'}") untersucht werden, um die potenziellen Unterschiede zwischen der Standard-Rendering-Funktion und benutzerdefinierten Rendering-Funktionen zu veranschaulichen.
Das tatsächliche Definieren eigener benutzerdefinierter Vorlagen-Rendering-Funktionen wäre dann ein separates, fortgeschritteneres Thema (ähnlich wie Studenten routinemäßig lernen, Dekoratoren und Kontextmanager zu verwenden, lange bevor sie lernen, eigene zu schreiben).
PEP 750 enthält weitere Ideen für Lehrmaterialien zum Thema verzögertes Rendering.
Diskussion
Siehe PEP 498 für frühere Diskussionen, da mehrere Punkte dort auch für diese PEP gelten. Die Design-Diskussionen von PEP 750 sind ebenfalls sehr relevant, da diese PEP mehrere Aspekte des aktuellen Designs inspiriert hat.
Unterstützung für binäre Interpolation
Da f-Strings keine Byte-Strings verarbeiten, werden t-Strings dies ebenfalls nicht tun.
Interoperabilität mit reinen str-Schnittstellen
Für Interoperabilität mit Schnittstellen, die nur Strings akzeptieren, können Interpolationsvorlagen immer noch mit format() vorab gerendert werden, anstatt das Rendering an die aufgerufene Funktion zu delegieren.
Dies spiegelt den Hauptunterschied zu PEP 498 wider, die *immer* das Standard-Rendering sofort anwendet, ohne Möglichkeit, die Wahl des Renderers an einen anderen Teil des Codes zu delegieren.
Beibehaltung des rohen Template-Strings
Frühere Versionen dieser PEP konnten die rohe Vorlagenzeichenkette nicht im Vorlagenliteral verfügbar machen. Ihre Beibehaltung ermöglicht eine attraktivere Vorlagendarstellung und die präzise Rekonstruktion der ursprünglichen Zeichenkette, einschließlich sowohl des Ausdruckstexts als auch der Details aller sofort gerenderten Substitutionsfelder in Format-Spezifizierern.
Erzeugung eines Rich-Objekts anstelle einer globalen Namensauflösung
Frühere Versionen dieser PEP verwendeten eine eingebaute Funktion __interpolate__, anstatt eine neue Art von Objekt für die spätere Verwendung durch Interpolationsfunktionen zu erstellen. Die Erstellung eines reichhaltigen beschreibenden Objekts mit einem nützlichen Standard-Renderer erleichterte die Unterstützung der Anpassung der Semantik der Interpolation erheblich.
Aufbauend auf f-Strings anstatt sie zu ersetzen
Frühere Versionen dieser PEP versuchten, ein vollständiger Ersatz für PEP 498 (f-Strings) zu sein. Mit der Akzeptanz dieser PEP und der neueren PEP 701 kann diese PEP stattdessen eine flexiblere Funktionalität für verzögertes Rendering aufbauend auf dem bestehenden sofortigen Rendering von f-Strings bereitstellen.
Die Annahme von f-Strings als unterstützende Fähigkeit vereinfachte eine Reihe von Aspekten des Vorschlags in dieser PEP (z. B. wie Substitutionsfelder in Format-Spezifizierern behandelt werden).
Definition von Wiederholungs- und Verkettungssemantik
Diese PEP definiert explizit Wiederholungs- und Verkettungssemantiken für TemplateLiteral und TemplateLiteralText. Obwohl nicht unbedingt erforderlich, wird erwartet, dass die Definition dieser Semantiken die Arbeit mit den Typen in Code, der historisch nur reguläre Strings unterstützte, erleichtert.
Neuer Konvertierungsspezifizierer für Lazy-Feld-Evaluierung
Die ursprünglich veröffentlichte Version von PEP 750 nutzte standardmäßig verzögerte Auswertung für alle Interpolationsfelder. Obwohl sie später aktualisiert wurde, um standardmäßig auf sofortige Auswertung zu setzen (wie bei f-Strings und dieser PEP), gaben die Diskussionen zu diesem Thema Anlass zur Idee, eine Möglichkeit bereitzustellen, Rendering-Funktionen anzuzeigen, dass der interpolierte Feldwert zum Rendering-Zeitpunkt aufgerufen werden soll, anstatt ihn unverändert zu verwenden.
Da PEP 750 auch die Verarbeitung von Konvertierungsspezifizierern bis zur Auswertungszeit verzögerte, wurde der Vorschlag gemacht, dass das Aufrufen von __call__ ohne Argumente ähnlich wie die vorhandenen Konvertierungsspezifizierer betrachtet werden könnte, die __repr__ (!a, !r) oder __str__ (!s) aufrufen.
Dementsprechend wurde diese PEP aktualisiert, um auch die Verarbeitung von Konvertierungsspezifizierern zur Verantwortung von Rendering-Funktionen zu machen und !() als neuen Konvertierungsspezifizierer für verzögerte Auswertung einzuführen.
Die Hinzufügung von operator.convert_field() und die Aktualisierung der eingebauten Funktion format() waren dann eine Frage der Bereitstellung angemessener Unterstützung für Rendering-Funktionsimplementierungen, die die Standard-Konvertierungsspezifizierer akzeptieren wollten.
Zulassen beliebiger Konvertierungsspezifizierer in benutzerdefinierten Renderern
Die Akzeptanz von !() als neuem Konvertierungsspezifizierer erfordert zwangsläufig die Aktualisierung der Syntax, die der Parser für Konvertierungsspezifizierer akzeptiert (sie sind derzeit auf Bezeichner beschränkt). Dies wirft dann die Frage auf, ob die Kompilierung von t-Strings die zusätzliche Einschränkung erzwingen sollte, die die Kompilierung von f-Strings auferlegt: dass der Konvertierungsspezifizierer genau einer von !a, !r oder !s sein muss.
Da t-Strings bereits aktualisiert werden, um !() bei der Kompilierung zuzulassen, war es sinnvoll, Konvertierungsspezifizierer in Bezug auf Rendering-Funktionen ähnlich zu behandeln, wie Format-Spezifizierer sich auf die Formatierung einzelner Objekte bezogen: Abgesehen von einigen Zeichen, die aus Parsing-Gründen ausgeschlossen sind, handelt es sich ansonsten um freie Textfelder, deren Bedeutung von der konsumierenden Funktion oder dem konsumierenden Objekt entschieden wird. Dies verringert die Versuchung, renderer-spezifisches Metatyping in die Format-Spezifizierer der Vorlage einzufügen (da jede renderer-spezifische Information stattdessen im Konvertierungsspezifizierer platziert werden kann).
Nur ein neuer String-Präfix reservieren
Der Hauptunterschied zwischen dieser PEP und PEP 750 besteht darin, dass letztere darauf abzielt, die Verwendung beliebiger String-Präfixe zu ermöglichen, anstatt die Erstellung von Vorlagenliteral-Instanzen zu verlangen, die dann an andere APIs übergeben werden. Zum Beispiel würde PEP 750 die in dieser PEP beschriebene sh-Renderung als sh"cat {somefile}" ermöglichen, anstatt zu verlangen, dass das Vorlagenliteral explizit erstellt und dann an einen regulären Funktionsaufruf übergeben wird (wie in sh(t"cat {somefile}")).
Der Hauptgrund, warum die Autoren der PEP die zweite Schreibweise bevorzugen, ist, dass sie einem Leser klarer macht, was passiert: eine Vorlagenliteral-Instanz wird erstellt und dann an ein aufrufbares Objekt übergeben, das weiß, wie es etwas Nützliches mit Interpolationsvorlagen-Instanzen tun kann.
Ein Entwurfsvorschlag eines der Autoren von PEP 750 legt auch nahe, dass statische Typüberprüfer die Verwendung bestimmter domänenspezifischer Sprachen genauso leicht aus der Form ableiten können, die einen expliziten Funktionsaufruf verwendet, wie sie sie aus einem direkt markierten String ableiten könnten.
Da die Syntax für markierte Strings die Klarheit für menschliche Leser bestenfalls fraglich reduziert, ohne die Gesamtausdruckskraft des Konstrukts zu erhöhen, scheint es vernünftig, mit dem kleinsten gangbaren Vorschlag (einem einzigen neuen String-Präfix) zu beginnen und dann den potenziellen Wert der Verallgemeinerung auf beliebige Präfixe in der Zukunft zu überprüfen.
Als geringfügigere, aber dennoch echte Erwägung, lässt die Verwendung eines einzigen neuen String-Präfixes für diesen Anwendungsfall die Möglichkeit offen, in Zukunft alternative Präfixe zu definieren, die weiterhin TemplateLiteral-Objekte erzeugen, aber eine andere Syntax innerhalb des Strings zur Definition der Interpolationsfelder verwenden (siehe die i18n-Diskussion unten).
Verschiebung der Berücksichtigung einer prägnanteren Syntax für verzögerte Auswertung
Während der Diskussionen über verzögerte Auswertung wurde {-> expr} als potenzieller syntaktischer Zucker für die bereits unterstützte lambda-basierte Syntax vorgeschlagen ({(lambda: expr)}) (die Klammern sind in der bestehenden Syntax erforderlich, um eine Fehlinterpretation des :-Zeichens als Beginn des Format-Spezifizierers zu vermeiden).
Obwohl das Hinzufügen einer solchen Schreibweise die in dieser PEP vorgeschlagene Syntax für Funktionsaufrufe zur Rendering-Zeit ergänzen würde (d. h. {-> expr!()} schreiben, um beliebige Ausdrücke zur Rendering-Zeit auszuwerten), ist dies ein Thema, das die Autoren der PEP eher einer zukünftigen PEP überlassen würden, falls diese PEP oder PEP 750 akzeptiert wird.
Verschiebung der Berücksichtigung einer möglichen Protokollintegration
Eine der Herausforderungen mit dem Logging-Modul bestand darin, dass wir bisher keine vernünftige Migrationsstrategie weg von der Verwendung von printf-ähnlichen Formaten entwickeln konnten. Obwohl das Logging-Modul es Formatierern erlaubt, die Verwendung von str.format()- oder string.Template-Stil-Substitutionen zu spezifizieren, kann es umständlich sein, sicherzustellen, dass auf diese Weise geschriebene Nachrichten nur von Log-Record-Formatierern verarbeitet werden, die diese Syntax erwarten.
Der Overhead für Laufzeit-Parsing und -Interpolation für Logging-Nachrichten stellt ebenfalls ein Problem für das umfangreiche Logging von Laufzeitereignissen für Überwachungszwecke dar.
Obwohl dies über den Rahmen dieser anfänglichen PEP hinausgeht, könnten Vorlagenliteral-Unterstützung potenziell zu den Event-Reporting-APIs des Logging-Moduls hinzugefügt werden, was die Erfassung relevanter Details in Formen wie ermöglicht
logging.debug(t"Event: {event}; Details: {data}")
logging.critical(t"Error: {error}; Details: {data}")
Anstelle des historischen Mod-Formatierungsstils
logging.debug("Event: %s; Details: %s", event, data)
logging.critical("Error: %s; Details: %s", event, data)
Da die Vorlagenliteral als reguläres Argument übergeben wird, bleiben auch andere Schlüsselwortargumente verfügbar
logging.critical(t"Error: {error}; Details: {data}", exc_info=True)
Der in dieser PEP beschriebene Ansatz zur Standardisierung der verzögerten Feld-Auswertung basiert hauptsächlich auf den erwarteten Anforderungen für diese hypothetische Integration in das Logging-Modul.
logging.debug(t"Eager evaluation of {expensive_call()}")
logging.debug(t"Lazy evaluation of {expensive_call!()}")
logging.debug(t"Eager evaluation of {expensive_call_with_args(x, y, z)}")
logging.debug(t"Lazy evaluation of {(lambda: expensive_call_with_args(x, y, z))!()}")
Es ist eine offene Frage, ob die Definition von Logging-Formatierern aktualisiert würde, um Vorlagen-Strings zu unterstützen, aber wenn dies der Fall wäre, wäre der wahrscheinlichste Weg, Felder zu definieren, die auf dem Log-Record nachgeschlagen und nicht sofort interpretiert werden sollen, einfach sie zu escapen, damit sie als Teil des Literal-Texts verfügbar sind.
proc_id = get_process_id()
formatter = logging.Formatter(t"{{asctime}}:{proc_id}:{{name}}:{{levelname}}{{message}}")
Verschiebung der Berücksichtigung einer möglichen Verwendung in i18n-Anwendungsfällen
Der ursprüngliche motivierende Anwendungsfall für dieses PEP war die Bereitstellung einer saubereren Syntax für die i18n (Internationalisierung) Übersetzung, da dies den Zugriff auf die ursprüngliche, unveränderte Vorlage erfordert. Daher konzentrierte es sich auf die Kompatibilität mit der Substitutionssyntax, die in Pythons string.Template Formatierung und Mozillas l20n-Projekt verwendet wird.
Nachfolgende Diskussionen ergaben jedoch, dass bei der i18n-Anwendung erhebliche zusätzliche Überlegungen angestellt werden müssen, die die einfacheren Fälle der Handhabung von Interpolation in sicherheitssensiblen Kontexten (wie HTML, System-Shells und Datenbankabfragen) oder der Erstellung von Anwendungssicherheitsfehlermeldungen in der bevorzugten Sprache des Entwicklungsteams (anstelle der Muttersprache der Endbenutzer) nicht beeinträchtigen.
Aufgrund dieser Erkenntnis wurde das PEP umgestellt, um die str.format() Substitutionssyntax zu verwenden, die ursprünglich in PEP 3101 definiert und anschließend als Grundlage für PEP 498 verwendet wurde.
Obwohl es theoretisch möglich wäre, string.Template zu aktualisieren, um die Erstellung von Instanzen aus nativen Vorlagen-Literalen zu unterstützen und das strukturelle typing.Template Protokoll zu implementieren, haben die Autoren des PEP keinen praktischen Nutzen darin erkannt.
Ein wesentlicher Vorteil des in diesem PEP verwendeten Ansatzes "nur ein String-Präfix" ist jedoch, dass er zwar die vorhandene f-String-Interpolationssyntax verallgemeinert, um verzögerte Rendering für t-Strings zu unterstützen, aber nicht impliziert, dass dies die *einzige* Compiler-unterstützte Interpolationssyntax sein sollte, die Python jemals anbieten sollte.
Insbesondere lässt er die Tür offen für eine alternative "t$-String"-Syntax, die es ermöglichen würde, TemplateLiteral Instanzen mit einer auf PEP 292 basierenden Interpolationssyntax anstelle einer auf PEP 3101 basierenden Syntax zu erstellen
template = t$”Substitute $words and ${other_values} at runtime”
Der einzige Laufzeitunterschied zwischen auf diese Weise erstellten Vorlagen und Vorlagen, die aus regulären t-Strings erstellt wurden, wäre der Inhalt ihrer raw_template Attribute.
Verschiebung der Unterstützung für escaped Rendering für Nicht-POSIX-Shells
shlex.quote() klassifiziert die Regex-Zeichenmenge [\w@%+=:,./-] als sicher, betrachtet alle anderen Zeichen als unsicher und erfordert daher die Anführungszeichensetzung des Strings, der sie enthält. Der verwendete Anführungszeichenmechanismus ist dann spezifisch für die Art und Weise, wie die String-Anführungszeichensetzung in POSIX-Shells funktioniert, so dass er nicht vertraut werden kann, wenn eine Shell ausgeführt wird, die nicht den POSIX-Shell-Anführungszeichenregeln folgt.
Beispielsweise ist die Ausführung von subprocess.run(f'echo {shlex.quote(sys.argv[1])}', shell=True) sicher, wenn eine Shell verwendet wird, die den POSIX-Anführungszeichenregeln folgt
$ cat > run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
$ python3 run_quoted.py pwd
pwd
$ python3 run_quoted.py '; pwd'
; pwd
$ python3 run_quoted.py "'pwd'"
'pwd'
bleibt aber unsicher, wenn eine Shell von Python aufgerufen wird, die cmd.exe (oder Powershell) aufruft
S:\> echo import sys, shlex, subprocess > run_quoted.py
S:\> echo subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True) >> run_quoted.py
S:\> type run_quoted.py
import sys, shlex, subprocess
subprocess.run(f"echo {shlex.quote(sys.argv[1])}", shell=True)
S:\> python3 run_quoted.py "echo OK"
'echo OK'
S:\> python3 run_quoted.py "'& echo Oh no!"
''"'"'
Oh no!'
Die Behebung dieser Einschränkung der Standardbibliothek liegt außerhalb des Umfangs dieses PEP.
Danksagungen
- Eric V. Smith für die Erstellung von PEP 498 und die Demonstration der Machbarkeit der beliebigen Ausdruckssubstitution bei der String-Interpolation
- Die Autoren von PEP 750 für die erheblichen Designverbesserungen, die getaggte Strings für dieses PEP inspiriert haben, ihre allgemeine Befürwortung des Werts der Unterstützung für verzögertes Vorlagenrendering auf Sprachebene und ihre Bemühungen, sicherzustellen, dass jede native Interpolationsvorlagenunterstützung eine starke Grundlage für zukünftige Bemühungen zur Bereitstellung robuster Syntaxhervorhebung und statischer Typüberprüfungsunterstützung für domänenspezifische Sprachen legt
- Barry Warsaw, Armin Ronacher und Mike Miller für ihre Beiträge zur Erforschung der Machbarkeit der Verwendung dieses Modells des verzögerten Renderings in i18n-Anwendungsfällen (auch wenn die endgültige Schlussfolgerung war, dass es sich schlecht eignet, zumindest für aktuelle Ansätze zur i18n in Python)
Referenzen
- %-Formatierung
- str.format
- Dokumentation zu string.Template
- PEP 215: String-Interpolation
- PEP 292: Einfachere String-Substitutionen
- PEP 3101: Erweiterte String-Formatierung
- PEP 498: Literal-String-Formatierung
- PEP 675: Beliebiger Literal-String-Typ
- PEP 701: Syntaktische Formalisierung von f-Strings
- FormattableString und C# native String-Interpolation
- IFormattable-Schnittstelle in C# (siehe Anmerkungen für Globalisierungsnotizen)
- TemplateLiterals in Javascript
- Ausführen externer Befehle in Julia
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-0501.rst
Zuletzt geändert: 2024-10-19 14:00:43 GMT