Zum Inhalt springen
aviral gupta

// A5.1 · ca. 30 Min. · Vertiefung

Template-Strings (t-Strings)

Nach dieser Lektion lesen Sie einen t-String als Template aus statischen Strings und Interpolationen, schreiben einen Renderer, der Konvertierungen und Formatangaben anwendet, und maskieren unsichere Werte, wo ein f-String sie vermischt hätte.

Lektion 1 von 5 in A5 Modernes Python und sicherer Code

Anfang des Moduls

Danach können Sie

  • Erklären, was ein t-String ergibt: ein Template mit strings, interpolations und values
  • Einen Renderer schreiben, der ein Template durchläuft und Konvertierung und Formatangabe anwendet
  • Erklären, warum ein Renderer unsichere Werte im t-String maskieren kann, im f-String aber nicht
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Modul A4 und der f-String-Lektion: Was gibt das aus?

    from decimal import Decimal
    
    price = Decimal("0.1") + Decimal("0.2")
    print(f"{price} {0.1 + 0.2:.2f}")
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: dieselben Klammern, aber mit t davor. Was gibt das aus?

    name = "Ada"
    greeting = t"Hello {name}!"
    print(type(greeting).__name__, greeting.strings)
  3. Üben · Aufgabe 3 von 7

    shout() schreibt nur die interpolierten Werte groß. Setzen Sie die Klasse ein, gegen die jeder Teil geprüft wird.

    from string.templatelib import Interpolation
    
    
    def shout(template):
        parts = []
        for part in template:
            if isinstance(part, ____):
                parts.append(str(part.value).upper())
            else:
                parts.append(part)
        return "".join(parts)
    if isinstance(part, ):
  4. Üben · Aufgabe 4 von 7

    Konvertierungen und Formatangaben werden gespeichert, nicht angewendet. Was gibt das aus?

    x = "hi"
    tpl = t"{x!r:>6}"
    i = tpl.interpolations[0]
    print(i.value, i.conversion, i.format_spec)
  5. Üben · Aufgabe 5 von 7

    x = 1 und y = 2. Ordnen Sie jedem Ausdruck seinen Wert zu.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Was gibt das aus?

    name = "Ada"
    greeting = t"Hi {name}!"
    print(greeting == "Hi Ada!", "".join(greeting.strings))
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe, eine strukturierte Logzeile. Schreiben Sie log_line(template): Es rendert den Text und hängt dann " | " und je Interpolation ein Paar expression=repr(value) an. log_line(t"{user} bought {n} books") mit user = "ada" und n = 3 muss ergeben: ada bought 3 books | user='ada' n=3

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Ein HTML-Renderer, der nur die Werte maskiert

render_html() durchläuft einen t-String. Es wendet jede Konvertierung und Formatangabe an wie ein f-String, maskiert das Ergebnis mit html.escape und übernimmt das statische Markup unverändert. Der Nutzername enthält spitze Klammern, wie sie ein Angreifer versuchen würde. Die letzte Zeile zeigt zum Vergleich die f-String-Fassung: Sobald alles ein str ist, lassen sich Markup des Programmierers und Text des Nutzers nicht mehr unterscheiden.

main.py

import html
from string.templatelib import Interpolation, Template, convert


def render_html(template: Template) -> str:
    """Join a template, escaping every interpolated value for HTML."""
    parts: list[str] = []
    for part in template:
        if isinstance(part, Interpolation):
            value = convert(part.value, part.conversion)
            text = format(value, part.format_spec)
            parts.append(html.escape(text))
        else:
            parts.append(part)  # static text, written by the programmer
    return "".join(parts)


user = "Ada <admin>"
total = 1234.5

page = t"<p>Hello {user}, you owe {total:,.2f} EUR</p>"
print(page.strings)
print(page.values)
print(render_html(page))
print(f"<p>Hello {user}</p>")  # an f-string has already merged the parts

Ausführen mit

python main.py

Ausgabe

('<p>Hello ', ', you owe ', ' EUR</p>')
('Ada <admin>', 1234.5)
<p>Hello Ada &lt;admin&gt;, you owe 1,234.50 EUR</p>
<p>Hello Ada <admin></p>
  • strings enthält die drei statischen Teile; values die zwei Werte, unformatiert.
  • Die Formatangabe ,.2f wendet format() im Renderer an, nicht der t-String.
  • Nur der Wert wird maskiert: <p> bleibt Markup, <admin> wird zu &lt;admin&gt;.
  • Die f-String-Zeile setzt den Nutzertext direkt ins Markup.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Rendern wie ein f-String

Vervollständigen Sie render(template) in main.py, sodass es genau das liefert, was der entsprechende f-String ergäbe. Der Starter setzt str(value) ein und ignoriert Konvertierung und Formatangabe. Wenden Sie part.conversion mit convert() an, danach part.format_spec mit format(). Die Tests vergleichen mit f-Strings, auch mit dem =-Spezifizierer.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    convert(part.value, part.conversion) gibt den Wert bei conversion None unverändert zurück, sonst str(), repr() oder ascii() davon.

  2. Hinweis 2

    format(value, "") entspricht str(value), format(konvertiert, part.format_spec) funktioniert also auch ohne Angabe.

  3. Hinweis 3

    Der =-Spezifizierer braucht keinen eigenen Code: Der t-String schreibt "n=" schon in strings und setzt conversion auf "r".

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

from string.templatelib import Interpolation, Template, convert


def render(template: Template) -> str:
    """Render a template exactly like the matching f-string."""
    out: list[str] = []
    for part in template:
        if isinstance(part, Interpolation):
            value = convert(part.value, part.conversion)
            out.append(format(value, part.format_spec))
        else:
            out.append(part)
    return "".join(out)
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

from string.templatelib import Interpolation, Template, convert


def render(template: Template) -> str:
    """Render a template exactly like the matching f-string."""
    out: list[str] = []
    for part in template:
        if isinstance(part, Interpolation):
            # Apply part.conversion (with convert) and part.format_spec (with format).
            out.append(str(part.value))
        else:
            out.append(part)
    return "".join(out)

test_main.py

from main import render


def test_plain():
    """Ein einfacher Wert wird unverändert eingesetzt"""
    name = "Ada"
    got = render(t"Hi {name}!")
    assert got == "Hi Ada!", f"render lieferte {got!r}, erwartet 'Hi Ada!'"


def test_format_spec():
    """Die Formatangabe wird angewendet, wie im f-String"""
    price = 3.14159
    got = render(t"{price:.2f} EUR")
    assert got == "3.14 EUR", f"render lieferte {got!r}: part.format_spec mit format() anwenden"


def test_conversion():
    """!r wird angewendet, wie im f-String"""
    word = "hi"
    got = render(t"say {word!r}")
    assert got == "say 'hi'", f"render lieferte {got!r}: part.conversion mit convert() anwenden"


def test_same_as_fstring():
    """Der =-Spezifizierer und eine Breite ergeben dasselbe wie der f-String"""
    n = 5
    got = render(t"{n=} [{n:>4}]")
    want = f"{n=} [{n:>4}]"
    assert got == want, f"render lieferte {got!r}, der f-String ergibt {want!r}"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Werte maskieren, Markup behalten

Vervollständigen Sie safe_html(template). Maskieren Sie jeden interpolierten Wert mit html.escape, nachdem Sie Konvertierung und Formatangabe angewendet haben, und lassen Sie das statische Markup unverändert. Ist das Argument kein Template, etwa der str aus einem f-String, lösen Sie TypeError aus: Ein Renderer, der einen str annimmt, wüsste nicht, welche Teile zu maskieren sind.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Beginnen Sie mit if not isinstance(template, Template): raise TypeError(...).

  2. Hinweis 2

    Durchlaufen Sie das Template. Eine Interpolation formatieren Sie wie in der vorigen Übung und hängen html.escape(text) an; einen str-Teil hängen Sie unverändert an.

  3. Hinweis 3

    html.escape maskiert <, > und & sowie standardmäßig beide Arten von Anführungszeichen.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import html
from string.templatelib import Interpolation, Template, convert


def safe_html(template: Template) -> str:
    """Build HTML from a t-string, escaping every interpolated value."""
    if not isinstance(template, Template):
        raise TypeError("safe_html needs a t-string, not a str")
    out: list[str] = []
    for part in template:
        if isinstance(part, Interpolation):
            text = format(convert(part.value, part.conversion), part.format_spec)
            out.append(html.escape(text))
        else:
            out.append(part)
    return "".join(out)
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

import html
from string.templatelib import Interpolation, Template, convert


def safe_html(template: Template) -> str:
    """Build HTML from a t-string, escaping every interpolated value."""
    # This escapes the static markup too, ignores the format specs,
    # and quietly accepts a plain str.
    text = "".join(str(part.value) if isinstance(part, Interpolation) else part for part in template)
    return html.escape(text)

test_main.py

from main import safe_html


def test_escapes_values():
    """Markup in einem Wert wird maskiert"""
    name = "<b>Ada</b>"
    got = safe_html(t"<p>{name}</p>")
    assert got == "<p>&lt;b&gt;Ada&lt;/b&gt;</p>", f"safe_html lieferte {got!r}"


def test_keeps_static_markup():
    """Das Markup im t-String selbst bleibt Markup"""
    city = "Bonn"
    got = safe_html(t'<a href="/map">{city}</a>')
    assert got == '<a href="/map">Bonn</a>', f"safe_html lieferte {got!r}: nur die Interpolationen maskieren"


def test_quotes_escaped():
    """Auch Anführungszeichen in einem Wert werden maskiert"""
    title = 'say "hi"'
    got = safe_html(t"<b>{title}</b>")
    assert got == "<b>say &quot;hi&quot;</b>", f"safe_html lieferte {got!r}"


def test_format_spec():
    """Die Formatangabe wird vor dem Maskieren angewendet"""
    total = 1234.5
    got = safe_html(t"<td>{total:,.2f}</td>")
    assert got == "<td>1,234.50</td>", f"safe_html lieferte {got!r}"


def test_rejects_str():
    """Ein einfacher str (ein fertiger f-String) löst TypeError aus"""
    name = "<b>Ada</b>"
    try:
        safe_html(f"<p>{name}</p>")
    except TypeError:
        pass
    else:
        assert False, "safe_html hat einen str angenommen; lösen Sie TypeError aus, wenn es kein Template ist"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Einen str an ein Template hängen

name = "Ada"
greeting = t"Hi {name}" + "!"

Was Python ausgibt

TypeError: can only concatenate string.templatelib.Template (not "str") to string.templatelib.Template

Warum, und die Lösung

Python kann nicht wissen, ob "!" statischer Text oder ein Wert sein soll, daher wird Template + str abgelehnt. Schreiben Sie den Text in den t-String, t"Hi {name}!", oder verpacken Sie ihn: + Template("!") für statischen Text, + Template(Interpolation(value, "name")) für einen Wert.

Einen t-String wie einen str behandeln

name = "Ada"
print(t"Hi {name}".upper())

Was Python ausgibt

AttributeError: 'string.templatelib.Template' object has no attribute 'upper'

Warum, und die Lösung

Ein Template hat keine String-Methoden, denn es ist noch kein Text. Rendern Sie es zuerst mit Ihrem eigenen Renderer und rufen Sie str-Methoden auf dem Ergebnis auf, oder ändern Sie die Werte, bevor sie in den t-String kommen.

Eine eigene Formatangabe an format() geben

name = "ada"
tpl = t"{name:upper}"
part = tpl.interpolations[0]
print(format(part.value, part.format_spec))

Was Python ausgibt

ValueError: Invalid format specifier 'upper' for object of type 'str'

Warum, und die Lösung

Ein t-String nimmt nach dem Doppelpunkt beliebigen Text an, denn der Renderer entscheidet, was er bedeutet. format() versteht nur die Standard-Minisprache. Erfindet Ihr Renderer eigene Angaben wie upper, muss er sie selbst behandeln, bevor er auf format() zurückgreift.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Ein t-String ist ein Template, kein str

t"Hello {name}!" hat die Syntax eines f-Strings, ergibt aber ein string.templatelib.Template, und noch ist nichts zusammengefügt. template.strings enthält die statischen Teile, ("Hello ", "!"), und hat immer ein Element mehr als template.interpolations; leere Strings füllen die Lücken, t"{a}{b}".strings ist also ("", "", ""). template.values enthält die ausgewerteten Werte. Beim Iterieren liefert ein Template seine nicht leeren Strings und Interpolation-Objekte der Reihe nach. Ein Template hat keine str-Methoden, und template + "text" löst TypeError aus: Offen wäre, ob "text" statisch oder ein Wert ist.

Interpolation: value, expression, conversion, format_spec

Jede Interpolation speichert den ausgewerteten Wert (value), den Ausdruckstext ("count * 2"), die Konvertierung ("r", "s", "a" oder None) und die format_spec (".2f", oder "" ohne Angabe). Anders als ein f-String wendet ein t-String weder Konvertierung noch Formatangabe an: Das entscheidet der Code, der das Template verarbeitet. Um einen f-String nachzubilden, rufen Sie convert(value, conversion) aus string.templatelib auf, dann format(ergebnis, format_spec). Da format_spec beliebiger Text sein darf, kann ein Renderer auch eigene Angaben definieren, darf unbekannte dann aber nicht an format() weitergeben.

Warum das bei unsicheren Eingaben zählt

Ein f-String wird sofort ausgewertet und liefert einen einzigen str. Eine Funktion, die f"<p>{name}</p>" bekommt, kann das Markup des Programmierers nicht vom Text des Nutzers unterscheiden und daher nicht gezielt den Nutzerteil maskieren. Ein Template hält beides bis zum Rendern getrennt: Der Renderer maskiert jede Interpolation (html.escape für HTML) und übernimmt die statischen Strings unverändert. Dasselbe Prinzip hilft bei SQL (jeder Wert wird zum ?-Platzhalter, wie Lektion A5.3 zeigen wird), Shell-Befehlen und strukturierten Logs. Ein guter Renderer weist einen einfachen str ab, damit niemand ihn mit einem f-String umgeht.

Quellen

Zuletzt geprüft am 29. September 2026