GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Consider making `Template` and `Interpolation` generic at runtime · Issue #133970 · python/cpython

Consider making Template and Interpolation generic at runtime #133970

New issue
Copy link
New issue
Copy link
Open
Open
Consider making Template and Interpolation generic at runtime#133970
Copy link
Assignees
sobolevn
Labels
3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-typingtriagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.type-featureA feature request or enhancementA feature request or enhancement

Description

@sobolevn
sobolevn
opened on May 13, 2025
Issue body actions

Feature or enhancement

Template and Interpolation will once be supported by type-checkers. And it might be worth adding generics support for these types.

Example

from string.templatelib import Template, Interpolation
from urllib.parse import quote_plus

domain = 'example.com'
query = 'python string formatting is too complex'
template = t'https://{domain}?q={query}'

def quote_url(interp: Interpolation[str]) -> str:
    return quote_plus(interp.value)

def format_url(template: Template) -> str:
    parts = []
    for part in template:
        match part:
            case str() as s:  # regular string
                parts.append(s)
            case Interpolation(str(), expression='query') as interp:
                parts.append(quote_url(interp))
            case Interpolation(value):
                parts.append(value)
    return ''.join(parts)

print(format_url(template))

Here we can see that Interpolation[str] can be useful in users' code.

We can also benefit from generic Template, where we can make it generic based on exact TypeVarTuple. Demo:

from typing import Any, TypeVarTuple, Generic, Unpack

Ts = TypeVarTuple('Ts', default=Unpack[tuple[Any, ...]])

class Template(Generic[*Ts]):
    def __new__(cls, *args: *Ts) -> Template[*Ts]: ...  # type: ignore[empty-body]
    
class Interpolation: ...
    
reveal_type(Template('a', 'b', Interpolation(), 'd'))
# Revealed type is "__main__.Template[Literal['a']?, Literal['b']?, __main__.Interpolation, Literal['d']?]"

https://mypy-play.net/?mypy=latest&python=3.13&gist=0dc13b3b926e1efb9783ab9b70d39ceb

This can potentially help type checkers to infer correct Template type.

Current state

Current typeshed definitions: https://github.com/python/typeshed/blob/main/stdlib/string/templatelib.pyi

Here's how Template is defined:

@final
class Template:  # TODO: consider making `Template` generic on `TypeVarTuple`
    strings: tuple[str, ...]
    interpolations: tuple[Interpolation, ...]

    def __new__(cls, *args: str | Interpolation) -> Template: ...
    def __iter__(self) -> Iterator[str | Interpolation]: ...
    def __add__(self, other: Template | str) -> Template: ...
    @property
    def values(self) -> tuple[Any, ...]: ...  # Tuple of interpolation values, which can have any type

Ideally, it should be generic on TypeVarTuple

Here's how Interpolation is defined:

@final
class Interpolation:
    value: Any  # TODO: consider making `Interpolation` generic in runtime
    expression: str
    conversion: Literal["a", "r", "s"] | None
    format_spec: str

    __match_args__ = ("value", "expression", "conversion", "format_spec")

    def __new__(
        cls, value: Any, expression: str, conversion: Literal["a", "r", "s"] | None = None, format_spec: str = ""
    ) -> Interpolation: ...

Ideally, it should be generic on value. Interpolation[str] means that value is str and Interpolation[tuple[int, int]] means that Interpolation has a value for type tuple[int, int]

Proposal

From runtime's part with only need to add Py_GenericAlias, that's it.

Is it too late for feature freeze? This is a minor detail, so I hope it is not :)

I have a PR ready.

CC @srittau @JelleZijlstra @lysnikolaou

Linked PRs

  • gh-133970: Make PEP750 types generic #133976
  • [3.14] gh-133970: Make PEP750 types generic (GH-133976) #134029
Reacted by Sebastian Rittau, Alistair Pattison and jaenw

Activity

Next
  1. sobolevn
    self-assigned this
    on May 13, 2025
  2. sobolevn
    added
    type-featureA feature request or enhancementA feature request or enhancement
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    topic-typing
    3.14bugs and security fixesbugs and security fixes
    on May 13, 2025
  3. srittau commented on May 13, 2025

    @srittau
    srittau
    on May 13, 2025
    Contributor
    More actions

    One alternative is to make Template generic over the interpolation value types instead. So __new__ would remain typed as it is, but type checkers could special case this (which they already have to do to support template strings) so that:

    foo: str = ""
    bar: int = 42
    tmpl = t"x{foo}{bar}y"
    assert_type(tmpl, Template[str, int])
    assert_type(tmpl.values, tuple[str, int])
    Reacted by sobolevn
  4. sobolevn commented on May 13, 2025

    @sobolevn
    sobolevn
    on May 13, 2025
    MemberAuthor
    More actions

    @srittau this would be also possible, if we just add Py_GenericAlias to Template. Exact typing would be determined later by type-checkers / typing council.

    Reacted by Sebastian Rittau
  5. JelleZijlstra commented on May 13, 2025

    @JelleZijlstra
    JelleZijlstra
    on May 13, 2025
    Member
    More actions

    Yes, I think we should do this, and it should be fine to do it during the beta phase as part of stabilizing the new feature.

    We'll have to decide in typeshed what the actual generic parameters are. I think it makes the most sense to use a single type parameter for both Template and Interpolation, so that for example Template[str] holds a series of Interpolation[str] and each has a value of type str.

  6. sobolevn commented on May 13, 2025

    @sobolevn
    sobolevn
    on May 13, 2025
    MemberAuthor
    More actions

    @JelleZijlstra ok, I will send a PR with Py_GenericAlias.

  7. sobolevn
    added a commit that references this issue on May 13, 2025

    pythongh-133970: Make PEP750 types generic

    5315168
  8. AA-Turner commented on May 13, 2025

    @AA-Turner
    AA-Turner
    on May 13, 2025
    Member
    More actions

    cc @lysnikolaou @davepeck (edit: Lysandros was alredy tagged, sorry!)

    Reacted by Dave Peck
  9. AA-Turner commented on May 13, 2025

    @AA-Turner
    AA-Turner
    on May 13, 2025
    Member
    More actions

    I think having Interpolation[T], where T is the type of i.value makes sense. For Template, my first reaction is that t'a {1} b {dict()} c {0.0}' could be type-hinted as Template[LiteralString, int, LiteralString, dict, ...]. This is very verbose though, and it would probably be annoying to type! Jelle's suggestion of a single parameter may be simpler in practice, though more restrictive for heterogeneous types.

    # Revealed type is "__main__.Template[Literal['a']?, Literal['b']?, __main__.Interpolation, Literal['d']?]"

    What do the question marks mean here? This isn't legal syntax.

    A

  10. sobolevn commented on May 13, 2025

    @sobolevn
    sobolevn
    on May 13, 2025
    MemberAuthor
    More actions

    @AA-Turner

    What do the question marks mean here? This isn't legal syntax.

    This is how mypy pretty-prints type when reveal_type is called. ? means that this type was inferenced, mostly.

  11. davepeck commented on May 14, 2025

    @davepeck
    davepeck
    on May 14, 2025
    Contributor
    More actions

    Considering the two suggested approaches:

    1. Template[T] implies that Template.interpolations is typed as tuple[Interpolation[T], ...]: I worry that real-world templates are nearly always heterogeneous and we'll end up with big unions.

    2. Template[A, B] implies Template.interpolations is typed as tuple[Interpolation[A], Interpolation[B]]: The types seem likely to get very large. Consider, say, building a full HTML page from a deeply nested collection of templates.


    Walking through a simple example:

    def user_image(user: User, classes: Sequence[str] = None):
        return t"<img src={user.image_url} alt={user.image_name} class={classes} />"
    
    def user_details(user: User, attribs: Mapping = None, classes: Sequence[str] = None):
        return t"""
            <div {attribs}>
                <span>{user.name} ({user.star_count})</span>
                {user_image(user, classes)}
            </div>
        """
    
    # What's the type of this `Template`?
    template = user_details(
        some_user, 
        {"aria-label": user.name, "aria-hidden": False}, 
        ["rounded"]
    )

    In this example:

    • {attribs} is a dict[str, str | bool]
    • {user.name} is a str
    • {user.star_count} is an int
    • {user_image(...)} is a nested Template
    • {classes} is list[str]

    In approach (1) we probably end up with Template[object] in practice?

    In approach (2) I suppose we get something like Template[dict[str, str | bool], str, int, Template[str, str, list[str]]]?

  12. JelleZijlstra commented on May 14, 2025

    @JelleZijlstra
    JelleZijlstra
    on May 14, 2025
    Member
    More actions

    I would think of this in terms of the type that you write if you accept a template. In your example, there might be a function render_html(template). If it can interpolate say str and int in a template, it would be typed as def render_html(template: Template[str | int]):. Type checkers can then tell you if you pass some object that isn't allowed as a template value.

  13. 4 remaining items

  14. TeamSpen210 commented on May 15, 2025

    @TeamSpen210
    TeamSpen210
    on May 15, 2025
    More actions

    The big unions might be alleviated a bit by aliases, especially if checkers can display them unexpanded. Probably they'll be necessary anyway, since you'd want to specify sub-templates recursively. For the default, object might be better - templates are immutable and therefore covariant, so that'll accept any template, but require users to verify interpolation values before use. It's inconsistent with other generics though...

  15. sobolevn
    added a commit that references this issue on May 15, 2025

    gh-133970: Make PEP750 types generic (#133976)

    c3a1da5
  16. miss-islington
    added a commit that references this issue on May 15, 2025

    pythongh-133970: Make PEP750 types generic (pythonGH-133976)

    bd2d229
  17. sobolevn commented on May 15, 2025

    @sobolevn
    sobolevn
    on May 15, 2025
    MemberAuthor
    More actions

    I merged the runtime generics PR, but I won't close the issue, we can continue talking about semantics here :)

  18. sobolevn
    added a commit that references this issue on May 15, 2025

    [3.14] gh-133970: Make PEP750 types generic (GH-133976) (#134029)

    f2a4b10
  19. lysnikolaou commented on May 15, 2025

    @lysnikolaou
    lysnikolaou
    on May 15, 2025
    Member
    More actions

    I'm not sure about this. Yes, Interpolation being generic makes sense, but Template is much harder and both choices that have been presented here are far from ideal and will mostly go unused in practice. I think we need to think bigger about this and, while I'm not the right person to do it, we might also want to explore options for how our type system could evolve around t-strings and the new options they open up.

    Tying ourselves to this specific design of making Template generic and then having to adhere to backwards-compatibility rules is not really something I'd like us to do. Maybe we should revert the PR and do it only for Interpolation for now?

  20. JelleZijlstra commented on May 15, 2025

    @JelleZijlstra
    JelleZijlstra
    on May 15, 2025
    Member
    More actions

    The runtime support is very simple; it just allows writing Template[whatever] creating a types.GenericAlias. It's up to type checkers and typeshed to decide what exactly will be allowed within the brackets, and we can decide that later without impacting backwards compatibility at the CPython level.

    It's hard to do much with a vague "we need to think bigger", but it seems likely that any type system support for templates will involve some variation of Template + square brackets, whether those square brackets hold the types of the interpolations or something else. Having this support in CPython now means that even if we specify a type system feature later, people will be able to use it even in CPython 3.14.

    I'd encourage you to open a discussion in the typing section on Discuss to brainstorm what this bigger thing would look like. What would be helpful would be examples of how templates are expected to be used and what kinds of things you'd like type checkers to be able to help you catch.

    Reacted by Sebastian Rittau
  21. sobolevn commented on May 15, 2025

    @sobolevn
    sobolevn
    on May 15, 2025
    MemberAuthor
    More actions

    Probably any reference implementation for any of the type-checkers will also help a lot to decide what is best for the static typing.

  22. lysnikolaou commented on May 15, 2025

    @lysnikolaou
    lysnikolaou
    on May 15, 2025
    Member
    More actions

    The runtime support is very simple; it just allows writing Template[whatever] creating a types.GenericAlias. It's up to type checkers and typeshed to decide what exactly will be allowed within the brackets, and we can decide that later without impacting backwards compatibility at the CPython level.

    Okay, I misunderstood the PR then and this looks good.

    It's hard to do much with a vague "we need to think bigger", but it seems likely that any type system support for templates will involve some variation of Template + square brackets, whether those square brackets hold the types of the interpolations or something else. Having this support in CPython now means that even if we specify a type system feature later, people will be able to use it even in CPython 3.14.

    I'd encourage you to open a discussion in the typing section on Discuss to brainstorm what this bigger thing would look like. What would be helpful would be examples of how templates are expected to be used and what kinds of things you'd like type checkers to be able to help you catch.

    I know that this is vague and I apologize for that. It's just that I can't really do better than that because I don't know what that "bigger thinking" might look like. There's ideas about combining type checking with static analysis for DSLs, but I don't think that's in scope for this thread.

    The recommendation to open a thread on Discuss is good. I also did a lightning talk in the language summit to get people to think about this stuff. I'll try to do it ASAP, but I'm not able to lead that discussion.

  23. erictraut commented on Jun 11, 2025

    @erictraut
    erictraut
    on Jun 11, 2025
    Contributor
    More actions

    Probably any reference implementation for any of the type-checkers will also help a lot to decide what is best for the static typing.

    Pyright 1.1.402 includes support for PEP 750. Here's an example.

    One small complication with making Template and Interpolation generic is that there is no way to explicitly specialize an Interpolation[T] by calling its constructor in the context of a t-string since its construction is implicitly called by the runtime. That means bidirectional type inference is the only option for influencing the type of the interpolations. I think this could work, but it's complicated by the concatenation rules for t-strings and regular strings (e.g. x: Template[int] = "foo" t"{bar}"). The inference context needs to span the concatenation.

    Reacted by Dave Peck
  24. JelleZijlstra commented on Jun 12, 2025

    @JelleZijlstra
    JelleZijlstra
    on Jun 12, 2025
    Member
    More actions

    Note there's still an open discussion about changing the implicit concatenation behavior that the SC needs to decide on: python/steering-council#289

  25. picnixz
    added
    triagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.
    on Jun 13, 2025
  26. picnixz commented on Jun 13, 2025

    @picnixz
    picnixz
    on Jun 13, 2025
    Member
    More actions

    (adding triaged so that I don't remove the 3.14 label by mistake)

  27. Pranjal095
    added a commit that references this issue on Jul 12, 2025

    pythongh-133970: Make PEP750 types generic (python#133976)

    cbfef51
  28. taegyunkim
    added a commit that references this issue on Aug 4, 2025

    pythongh-133970: Make PEP750 types generic (python#133976)

    96a184c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @sobolevn
    sobolevn

Labels

3.14bugs and security fixesbugs and security fixesinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-typingtriagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.type-featureA feature request or enhancementA feature request or enhancement

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.