Brett Cannon recently released a great article explaining how Python 3.14’s new t-strings work.

Here’s the article: Unravelling t-strings.

He built up the functionality of how t-strings work in a way that you can follow along even if you don’t have 3.14.0b1 (where t-strings are instroduced), all the way up to the last example.

He walks through

  • Evaluating the Python expression
  • Applying specified conversions
  • Applying format specs
  • Using an Interpolation class to hold details of replacement fields
  • Using Template class to hold parsed data

The end result is very close to an example used in PEP 750.

All very cool, and helpful.
But in small command line files.

I wanted to try them all at once, so I converted the examples to pytest tests.
Now I can run them all together.

Actually, this process also helped me pay close attention to the article, so I learned it even more.
Thanks, Brett, for this tutorial.

My code is also in a gist, as well as here:

import pytest
import sys

# shared by all examples
name = "world"
expected = f"Hello, {name}! Conversions like {name!r} and format specs like {name:<6} work!"


def test_identity():
    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        return t_string

    actual = f_yeah(expected)
    print('\nactual:', actual)

    assert actual == expected


def test_parse_split_join():
    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        return "".join(t_string)
    parsed = [
        "Hello, ",
        "world",
        "! Conversions like ",
        "'world'",
        " and format specs like ",
        "world ",
        " work!",
    ]
    actual = f_yeah(parsed)

    assert actual == expected


def test_parse_split_join_with_replacement():
    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        return "".join(t_string)
    parsed = [
        "Hello, ",
        name,
        "! Conversions like ",
        repr(name),
        " and format specs like ",
        format(name, "<6"),
        " work!",
    ]
    actual = f_yeah(parsed)

    assert actual == expected


def test_parse_parts():
    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        converters = {func.__name__[0]: func for func in (str, repr, ascii)}
        converters[None] = str

        parts = []
        for part in t_string:
            match part:
                case (value, conversion, format_spec):
                    parts.append(format(converters[conversion](value), format_spec))
                case str():
                    parts.append(part)

        return "".join(parts)

    parsed = [
        "Hello, ",
        (name, None, ""),
        "! Conversions like ",
        (name, "r", ""),
        " and format specs like ",
        (name, None, "<6"),
        " work!",
    ]
    actual = f_yeah(parsed)

    assert actual == expected


def test_parse_with_string_rep():
    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        converters = {func.__name__[0]: func for func in (str, repr, ascii)}
        converters[None] = str

        parts = []
        for part in t_string:
            match part:
                case (value, _, conversion, format_spec):
                    parts.append(format(converters[conversion](value), format_spec))
                case str():
                    parts.append(part)

        return "".join(parts)

    parsed = [
        "Hello, ",
        (name, "name", None, ""),
        "! Conversions like ",
        (name, "name", "r", ""),
        " and format specs like ",
        (name, "name", None, "<6"),
        " work!",
    ]
    actual = f_yeah(parsed)

    assert actual == expected

def test_parse_with_interpolation():
    class Interpolation:
        __match_args__ = ("value", "expression", "conversion", "format_spec")

        def __init__(
            self,
            value,
            expression,
            conversion=None,
            format_spec="",
        ):
            self.value = value
            self.expression = expression
            self.conversion = conversion
            self.format_spec = format_spec


    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        converters = {func.__name__[0]: func for func in (str, repr, ascii)}
        converters[None] = str

        parts = []
        for part in t_string:
            match part:
                case Interpolation(value, _, conversion, format_spec):
                    parts.append(format(converters[conversion](value), format_spec))
                case str():
                    parts.append(part)

        return "".join(parts)


    parsed = [
        "Hello, ",
        Interpolation(name, "name"),
        "! Conversions like ",
        Interpolation(name, "name", "r"),
        " and format specs like ",
        Interpolation(name, "name", format_spec="<6"),
        " work!",
    ]
    actual = f_yeah(parsed)

    assert actual == expected


def test_parse_with_inetrpolation_and_template():
    class Interpolation:
        __match_args__ = ("value", "expression", "conversion", "format_spec")

        def __init__(
            self,
            value,
            expression,
            conversion=None,
            format_spec="",
        ):
            self.value = value
            self.expression = expression
            self.conversion = conversion
            self.format_spec = format_spec


    class Template:
        def __init__(self, *args):
            # There will always be N+1 strings for N interpolations;
            # that may mean inserting an empty string at the start or end.
            strings = []
            interpolations = []
            if args and isinstance(args[0], Interpolation):
                strings.append("")
            for arg in args:
                match arg:
                    case str():
                        strings.append(arg)
                    case Interpolation():
                        interpolations.append(arg)
            if args and isinstance(args[-1], Interpolation):
                strings.append("")

            self._iter = args
            self.strings = tuple(strings)
            self.interpolations = tuple(interpolations)

        @property
        def values(self):
            return tuple(interpolation.value for interpolation in self.interpolations)

        def __iter__(self):
            return iter(self._iter)


    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        converters = {func.__name__[0]: func for func in (str, repr, ascii)}
        converters[None] = str

        parts = []
        for part in t_string:
            match part:
                case Interpolation(value, _, conversion, format_spec):
                    parts.append(format(converters[conversion](value), format_spec))
                case str():
                    parts.append(part)

        return "".join(parts)


    parsed = Template(
        "Hello, ",
        Interpolation(name, "name"),
        "! Conversions like ",
        Interpolation(name, "name", "r"),
        " and format specs like ",
        Interpolation(name, "name", format_spec="<6"),
        " work!",
    )
    actual = f_yeah(parsed)

    assert actual == expected


@pytest.mark.skipif(sys.version_info < (3, 14, 0, 'beta', 1), reason="requires python3.14b1 or higher")
def test_tstring():
    from string import templatelib

    def f_yeah(t_string):
        """Convert a t-string into what an f-string would have provided."""
        converters = {func.__name__[0]: func for func in (str, repr, ascii)}
        converters[None] = str

        parts = []
        for part in t_string:
            match part:
                case templatelib.Interpolation(value, _, conversion, format_spec):
                    parts.append(format(converters[conversion](value), format_spec))
                case str():
                    parts.append(part)

        return "".join(parts)


    parsed = t"Hello, {name}! Conversions like {name!r} and format specs like {name:<6} work!"
    actual = f_yeah(parsed)
    print(f'\nversion: {sys.version_info}')

    assert actual == expected

Learn pytest fast!