Template TypeInterpolation TypeTemplate.values PropertyTemplate Contents=)Template.__str__() Implementationstring.templatelib ModuleTemplate andInterpolation Into Protocols__eq__ and__hash__ forTemplate andInterpolationDecoded TypeTemplate andInterpolationconversion FromInterpolationTemplateImportant
This PEP is a historical document. The up-to-date, canonical documentation can now be found atTemplate strings.
×
SeePEP 1 for how to propose changes.
This PEP introduces template strings for custom string processing.
Template strings are a generalization of f-strings, using at in place ofthef prefix. Instead of evaluating tostr, t-strings evaluate to a newtype,Template:
template:Template=t"Hello{name}"
Templates provide developers with access to the string and its interpolatedvaluesbefore they are combined. This brings native flexible stringprocessing to the Python language and enables safety checks, web templating,domain-specific languages, and more.
Python introduced f-strings in Python 3.6 withPEP 498. The grammar wasthen formalized inPEP 701 which also lifted some restrictions. This PEPis based on PEP 701.
At nearly the same time PEP 498 arrived,PEP 501 was written to provide“i-strings” – that is, “interpolation template strings”. The PEP wasdeferred pending further experience with f-strings. Work on this PEP wasresumed by a different author in March 2023, introducing “t-strings” as templateliteral strings, and built atop PEP 701.
The authors of this PEP consider it to be a generalization and simplificationof the updated work in PEP 501. (That PEP has also recently been updated toreflect the new ideas in this PEP.)
Python f-strings are easy to use and very popular. Over time, however, developershave encountered limitations that make themunsuitable for certain use cases.In particular, f-strings provide no way to intercept and transform interpolatedvalues before they are combined into a final string.
As a result, incautious use of f-strings can lead to security vulnerabilities.For example, a user executing a SQL query withsqlite3may be tempted to use an f-string to embed values into their SQL expression,which could lead to aSQL injection attack.Or, a developer building HTML may include unescaped user input in the string,leading to across-site scripting (XSS)vulnerability.
More broadly, the inability to transform interpolated values before they arecombined into a final string limits the utility of f-strings in more complexstring processing tasks.
Template strings address these problems by providingdevelopers with access to the string and its interpolated values.
For example, imagine we want to generate some HTML. Using template strings,we can define anhtml() function that allows us to automatically sanitizecontent:
evil="<script>alert('evil')</script>"template=t"<p>{evil}</p>"asserthtml(template)=="<p><script>alert('evil')</script></p>"
Likewise, our hypotheticalhtml() function can make it easy for developersto add attributes to HTML elements using a dictionary:
attributes={"src":"shrubbery.jpg","alt":"looks nice"}template=t"<img{attributes} />"asserthtml(template)=='<img src="shrubbery.jpg" alt="looks nice" />'
Neither of these examples is possible with f-strings. By providing amechanism to intercept and transform interpolated values, template stringsenable a wide range of string processing use cases.
This PEP introduces a new string prefix,t, to define template string literals.These literals resolve to a new type,Template, found in the standard librarymodulestring.templatelib.
The following code creates aTemplate instance:
fromstring.templatelibimportTemplatetemplate=t"This is a template string."assertisinstance(template,Template)
Template string literals support the full syntax ofPEP 701. This includesthe ability to nest template strings within interpolations, as well as the abilityto use all valid quote marks (',",''', and"""). Like other stringprefixes, thet prefix must immediately precede the quote. Like f-strings,both lowercaset and uppercaseT prefixes are supported. Likef-strings, t-strings may not be combined withu or theb prefix.
Additionally, f-strings and t-strings cannot be combined, so theftprefix is invalid. t-stringsmay be combined with ther prefix;see theRaw Template Strings section below for more information.
Template TypeTemplate strings evaluate to an instance of a new immutable type,string.templatelib.Template:
classTemplate:strings:tuple[str,...]""" A non-empty tuple of the string parts of the template, with N+1 items, where N is the number of interpolations in the template. """interpolations:tuple[Interpolation,...]""" A tuple of the interpolation parts of the template. This will be an empty tuple if there are no interpolations. """def__new__(cls,*args:str|Interpolation):""" Create a new Template instance. Arguments can be provided in any order. """...@propertydefvalues(self)->tuple[object,...]:""" Return a tuple of the `value` attributes of each Interpolation in the template. This will be an empty tuple if there are no interpolations. """...def__iter__(self)->Iterator[str|Interpolation]:""" Iterate over the string parts and interpolations in the template. These may appear in any order. Empty strings will not be included. """...
Thestrings andinterpolations attributes provide access to the stringparts and any interpolations in the literal:
name="World"template=t"Hello{name}"asserttemplate.strings[0]=="Hello "asserttemplate.interpolations[0].value=="World"
Interpolation TypeTheInterpolation type represents an expression inside a template string.LikeTemplate, it is a new class found in thestring.templatelib module:
classInterpolation:value:objectexpression:strconversion:Literal["a","r","s"]|Noneformat_spec:str__match_args__=("value","expression","conversion","format_spec")def__new__(cls,value:object,expression:str="",conversion:Literal["a","r","s"]|None=None,format_spec:str="",):...
TheInterpolation type is shallow immutable. Its attributescannot be reassigned.
Thevalue attribute is the evaluated result of the interpolation:
name="World"template=t"Hello{name}"asserttemplate.interpolations[0].value=="World"
When interpolations are created from a template string literal, theexpression attribute contains theoriginal text of the interpolation:
name="World"template=t"Hello{name}"asserttemplate.interpolations[0].expression=="name"
When developers explicitly construct anInterpolation, they may optionallyprovide a value for theexpression attribute. Even though it is stored asa string, thisshould be a valid Python expression. If no value is provided,theexpression attribute defaults to the empty string ("").
We expect that theexpression attribute will not be used in most templateprocessing code. It is provided for completeness and for use in debugging andintrospection. See both theCommon Patterns Seen in Processing Templatessection and theExamples section for more information on how to processtemplate strings.
Theconversion attribute is theoptional conversionto be used, one ofr,s, anda, corresponding torepr(),str(), andascii() conversions. As with f-strings, no other conversionsare supported:
name="World"template=t"Hello{name!r}"asserttemplate.interpolations[0].conversion=="r"
If no conversion is provided,conversion isNone.
Theformat_spec attribute is theformat specification.As with f-strings, this is an arbitrary string that defines how to present the value:
value=42template=t"Value:{value:.2f}"asserttemplate.interpolations[0].format_spec==".2f"
Format specifications in f-strings can themselves contain interpolations. Thisis permitted in template strings as well;format_spec is set to the eagerlyevaluated result:
value=42precision=2template=t"Value: {value:.{precision}f}"asserttemplate.interpolations[0].format_spec==".2f"
If no format specification is provided,format_spec defaults to an emptystring (""). This matches theformat_spec parameter of Python’sformat() built-in.
Unlike f-strings, it is up to code that processes the template to determine how tointerpret theconversion andformat_spec attributes.Such code is not required to use these attributes, but when present they shouldbe respected, and to the extent possible match the behavior of f-strings.It would be surprising if, for example, a template string that uses{value:.2f}did not round the value to two decimal places when processed.
Template.values PropertyTheTemplate.values property is a shortcut for accessing thevalueattribute of eachInterpolation in the template and is equivalent to:
@propertydefvalues(self)->tuple[object,...]:returntuple(i.valueforiinself.interpolations)
Template ContentsTheTemplate.__iter__() method provides a simple way to access the fullcontents of a template. It yields the string parts and interpolations inthe order they appear, with empty strings omitted.
The__iter__() method is equivalent to:
def__iter__(self)->Iterator[str|Interpolation]:fors,iinzip_longest(self.strings,self.interpolations):ifs:yieldsifi:yieldi
The following examples show the__iter__() method in action:
assertlist(t"")==[]assertlist(t"Hello")==["Hello"]name="World"template=t"Hello{name}!"contents=list(template)assertlen(contents)==3assertcontents[0]=="Hello "assertcontents[1].value=="World"assertcontents[1].expression=="name"assertcontents[2]=="!"
Empty strings, which may be present inTemplate.strings, are not includedin the output of the__iter__() method:
first="Eat"second="Red Leicester"template=t"{first}{second}"contents=list(template)assertlen(contents)==2assertcontents[0].value=="Eat"assertcontents[0].expression=="first"assertcontents[1].value=="Red Leicester"assertcontents[1].expression=="second"# However, the strings attribute contains empty strings:asserttemplate.strings==("","","")
Template processing code can choose to work with any combination ofstrings,interpolations,values, and__iter__() based onrequirements and convenience.
Developers can write arbitrary code to process template strings. For example,the following function renders static parts of the template in lowercase andinterpolations in uppercase:
fromstring.templatelibimportTemplate,Interpolationdeflower_upper(template:Template)->str:"""Render static parts lowercased and interpolations uppercased."""parts:list[str]=[]foritemintemplate:ifisinstance(item,Interpolation):parts.append(str(item.value).upper())else:parts.append(item.lower())return"".join(parts)name="world"assertlower_upper(t"HELLO{name}")=="hello WORLD"
There is no requirement that template strings are processed in any particularway. Code that processes templates has no obligation to return a string.Template strings are a flexible, general-purpose feature.
See theCommon Patterns Seen in Processing Templates section for moreinformation on how to process template strings. See theExamples sectionfor detailed working examples.
Template strings support explicit concatenation using+. Concatenation issupported for twoTemplate instances viaTemplate.__add__():
name="World"assertisinstance(t"Hello "+t"{name}",Template)assert(t"Hello "+t"{name}").strings==("Hello ","")assert(t"Hello "+t"{name}").values[0]=="World"
Implicit concatenation of two template string literals is also supported:
name="World"assertisinstance(t"Hello "t"{name}",Template)assert(t"Hello "t"{name}").strings==("Hello ","")assert(t"Hello "t"{name}").values[0]=="World"
Both implicit and explicit concatenation ofTemplate andstr isprohibited. This is because it is ambiguous whether thestr should betreated as a static string part or as an interpolation.
To combine aTemplate and astr, developers must explicitly decide howto treat thestr. If thestr is intended to be a static string part,it should be wrapped in aTemplate. If thestr is intended to be aninterpolation value, it should be wrapped in anInterpolation andpassed to theTemplate constructor. For example:
name="World"# Treat `name` as a static string parttemplate=t"Hello "+Template(name)# Treat `name` as an interpolationtemplate=t"Hello "+Template(Interpolation(name,"name"))
Template andInterpolation instances compare with object identity(is).
Template instances are intended to be used by template processing code,which may return a string or any other type. Those types can provide theirown equality semantics as needed.
TheTemplate andInterpolation types do not support ordering. This isunlike all other string literal types in Python, which support lexicographicordering. Because interpolations can contain arbitrary values, there is nonatural ordering for them. As a result, neither theTemplate nor theInterpolation type implements the standard comparison methods.
=)The debug specifier,=, is supported in template strings and behaves similarlyto how it behaves in f-strings, though due to limitations of the implementationthere is a slight difference.
In particular,t'{value=}' is treated ast'value={value!r}'. The firststatic string is rewritten from"" to"value=" and theconversiondefaults tor:
name="World"template=t"Hello {name=}"asserttemplate.strings[0]=="Hello name="asserttemplate.interpolations[0].value=="World"asserttemplate.interpolations[0].conversion=="r"
If a conversion is explicitly provided, it is kept:t'{value=!s}'is treated ast'value={value!s}'.
If a format string is provided without a conversion, theconversionis set toNone:t'{value=:fmt}' is treated ast'value={value:fmt}'.
Whitespace is preserved in the debug specifier, sot'{value=}' istreated ast'value={value!r}'.
Raw template strings are supported using thert (ortr) prefix:
trade='shrubberies'template=rt'Did you say "{trade}"?\n'asserttemplate.strings[0]==r'Did you say "'asserttemplate.strings[1]==r'"?\n'
In this example, the\n is treated as two separate characters(a backslash followed by ‘n’) rather than a newline character. This isconsistent with Python’s raw string behavior.
As with regular template strings, interpolations in raw template strings areprocessed normally, allowing for the combination of raw string behavior anddynamic content.
Expression evaluation for interpolations is the same as inPEP 498:
The expressions that are extracted from the string are evaluated in the contextwhere the template string appeared. This means the expression has full access to itslexical scope, including local and global variables. Any valid Python expressioncan be used, including function and method calls.
Template strings are evaluated eagerly from left to right, just like f-strings. This means thatinterpolations are evaluated immediately when the template string is processed, not deferredor wrapped in lambdas.
Exceptions raised in t-string literals are the same as those raised in f-stringliterals.
Template.__str__() ImplementationTheTemplate type does not provide a specialized__str__() implementation.
This is becauseTemplate instances are intended to be used by template processingcode, which may return a string or any other type. There is no canonical way toconvert a Template to a string.
TheTemplate andInterpolation types both provide useful__repr__()implementations.
string.templatelib ModuleThestring module will be converted into a package, with a newtemplatelib submodule containing theTemplate andInterpolationtypes. Following the implementation of this PEP, this new module may be usedfor related functions, such asconvert(), or potential future templateprocessing code, such as shell script helpers.
All examples in this section of the PEP have fully tested reference implementationsavailable in the publicpep750-examplesgit repository.
It is easy to “implement” f-strings using t-strings. That is, we canwrite a functionf(template:Template)->str that processes aTemplatein much the same way as an f-string literal, returning the same result:
name="World"value=42templated=t"Hello{name!r}, value:{value:.2f}"formatted=f"Hello{name!r}, value:{value:.2f}"assertf(templated)==formatted
Thef() function supports both conversion specifiers like!r and formatspecifiers like:.2f. The full code is fairly simple:
fromstring.templatelibimportTemplate,Interpolationdefconvert(value:object,conversion:Literal["a","r","s"]|None)->object:ifconversion=="a":returnascii(value)elifconversion=="r":returnrepr(value)elifconversion=="s":returnstr(value)returnvaluedeff(template:Template)->str:parts=[]foritemintemplate:matchitem:casestr()ass:parts.append(s)caseInterpolation(value,_,conversion,format_spec):value=convert(value,conversion)value=format(value,format_spec)parts.append(value)return"".join(parts)
Structured logging allows developers to log data in machine-readableformats like JSON. With t-strings, developers can easily log structured dataalongside human-readable messages using just a single log statement.
We present two different approaches to implementing structured logging withtemplate strings.
ThePython Logging Cookbookhas a short section onhow to implement structured logging.
The logging cookbook suggests creating a new “message” class,StructuredMessage,that is constructed with a simple text message and a separate dictionary of values:
message=StructuredMessage("user action",{"action":"traded","amount":42,"item":"shrubs"})logging.info(message)# Outputs:# user action >>> {"action": "traded", "amount": 42, "item": "shrubs"}
TheStructuredMessage.__str__() method formats both the human-readablemessageand the values, combining them into a final string. (See thelogging cookbookfor its full example.)
We can implement an improved version ofStructuredMessage using template strings:
importjsonfromstring.templatelibimportInterpolation,TemplatefromtypingimportMappingclassTemplateMessage:def__init__(self,template:Template)->None:self.template=template@propertydefmessage(self)->str:# Use the f() function from the previous examplereturnf(self.template)@propertydefvalues(self)->Mapping[str,object]:return{item.expression:item.valueforiteminself.templateifisinstance(item,Interpolation)}def__str__(self)->str:returnf"{self.message} >>>{json.dumps(self.values)}"_=TemplateMessage# optional, to improve readabilityaction,amount,item="traded",42,"shrubs"logging.info(_(t"User{action}:{amount:.2f}{item}"))# Outputs:# User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}
Template strings give us a more elegant way to define the custom messageclass. With template strings it is no longer necessary for developers to makesure that their format string and values dictionary are kept in sync; a singletemplate string literal is all that is needed. TheTemplateMessageimplementation can automatically extract structured keys and values fromtheInterpolation.expression andInterpolation.value attributes,respectively.
Custom messages are a reasonable approach to structured logging but can be alittle awkward. To use them, developers must wrap every log message they writein a custom class. This can be easy to forget.
An alternative approach is to define customlogging.Formatter classes. Thisapproach is more flexible and allows for more control over the final output. Inparticular, it’s possible to take a single template string and output it inmultiple formats (human-readable and JSON) to separate log streams.
We define two simple formatters, aMessageFormatter for human-readable outputand aValuesFormatter for JSON output:
importjsonfromloggingimportFormatter,LogRecordfromstring.templatelibimportInterpolation,TemplatefromtypingimportAny,MappingclassMessageFormatter(Formatter):defmessage(self,template:Template)->str:# Use the f() function from the previous examplereturnf(template)defformat(self,record:LogRecord)->str:msg=record.msgifnotisinstance(msg,Template):returnsuper().format(record)returnself.message(msg)classValuesFormatter(Formatter):defvalues(self,template:Template)->Mapping[str,Any]:return{item.expression:item.valueforitemintemplateifisinstance(item,Interpolation)}defformat(self,record:LogRecord)->str:msg=record.msgifnotisinstance(msg,Template):returnsuper().format(record)returnjson.dumps(self.values(msg))
We can then use these formatters when configuring our logger:
importloggingimportsyslogger=logging.getLogger(__name__)message_handler=logging.StreamHandler(sys.stdout)message_handler.setFormatter(MessageFormatter())logger.addHandler(message_handler)values_handler=logging.StreamHandler(sys.stderr)values_handler.setFormatter(ValuesFormatter())logger.addHandler(values_handler)action,amount,item="traded",42,"shrubs"logger.info(t"User{action}:{amount:.2f}{item}")# Outputs to sys.stdout:# User traded: 42.00 shrubs# At the same time, outputs to sys.stderr:# {"action": "traded", "amount": 42, "item": "shrubs"}
This approach has a couple advantages over the custom message approach to structuredlogging:
This PEP contains several short HTML templating examples. It turns out that the“hypothetical”html() function mentioned in theMotivation section(and a few other places in this PEP) exists and is available in thepep750-examples repository.If you’re thinking about parsing a complex grammar with template strings, wehope you’ll find it useful.
Like f-strings, use of template strings will be a syntactic backwards incompatibilitywith previous versions.
The security implications of working with template strings, with respect tointerpolations, are as follows:
Template instances can ensure that any interpolationsare processed in a safe fashion, including respecting the context in whichthey appear.Template strings have several audiences:
We hope that teaching developers will be straightforward. At a glance,template strings look just like f-strings. Their syntax is familiar and thescoping rules remain the same.
The first thing developers must learn is that template string literals don’tevaluate to strings; instead, they evaluate to a new type,Template. Thisis a simple type intended to be used by template processing code. It’s not untildevelopers call a processing function that they get the result they want:typically, a string, although processing code can of course return any arbitrarytype.
Developers will also want to understand how template strings relate to otherstring formatting methods like f-strings andstr.format(). They will needto decide when to use each method. If a simple string is all that is needed, andthere are no security implications, f-strings are likely the best choice. Formost cases where a format string is used, it can be replaced with a functionwrapping the creation of a template string. In cases where the format string isobtained from user input, the filesystem, or databases, it is possible to writecode to convert it into aTemplate instance if desired.
Because developers will learn that t-strings are nearly always used in tandemwith processing functions, they don’t necessarily need to understand the detailsof theTemplate type. As with descriptors and decorators, we expect many moredevelopers will use t-strings than write t-string processing functions.
Over time, a small number of more advanced developerswill wish to author theirown template processing code. Writing processing code often requires thinkingin terms of formal grammars. Developers will need to learn how to work with thestrings andinterpolation attributes of aTemplate instance and howto process interpolations in a context-sensitive fashion. More sophisticatedgrammars will likely require parsing to intermediate representations like anabstract syntax tree (AST). Great template processing code will handle formatspecifiers and conversions when appropriate. Writing production-grade templateprocessing code – for instance, to support HTML templates – can be a largeundertaking.
We expect that template strings will provide framework authors with a powerfulnew tool in their toolbox. While the functionality of template strings overlapswith existing tools like template engines, t-strings move that logic intothe language itself. Bringing the full power and generality of Python to bear onstring processing tasks opens new possibilities for framework authors.
The world of Python already has mature templating languages with wide adoption,such as Jinja. Why build support for creating new templating systems?
Projects such as Jinja are still needed in cases where the template is less partof the software by the developers, and more part of customization by designersor even content created by users, for example in a CMS.
The trends in frontend development have treated templating as part of thesoftware and written by developers. They want modern language features and agood tooling experience. PEP 750 envisions DSLs where the non-static parts arePython: same scope rules, typing, expression syntax, and the like.
Iterating over theTemplate with structural pattern matching is the expectedbest practice for many template function implementations:
fromstring.templatelibimportTemplate,Interpolationdefprocess(template:Template)->Any:foritemintemplate:matchitem:casestr()ass:...# handle each string partcaseInterpolation()asinterpolation:...# handle each interpolation
Processing code may also commonly sub-match on attributes of theInterpolation type:
matcharg:caseInterpolation(int()):...# handle interpolations with integer valuescaseInterpolation(value=str()ass):...# handle interpolations with string values# etc.
Template functions can efficiently process both static and dynamic parts of templates.The structure ofTemplate objects allows for effective memoization:
strings=template.strings# Static string partsvalues=template.values# Dynamic interpolated values
This separation enables caching of processed static parts while dynamic partscan be inserted as needed. Authors of template processing code can use the staticstrings as cache keys, leading to significant performance improvements whensimilar templates are used repeatedly.
Code that processes templates can parse the template string into intermediaterepresentations, like an AST. We expect that many template processing librarieswill use this approach.
For instance, rather than returning astr, our theoreticalhtml() function(see theMotivation section) could return an HTMLElement defined in thesame package:
@dataclass(frozen=True)classElement:tag:strattributes:Mapping[str,str|bool]children:Sequence[str|Element]def__str__(self)->str:...defhtml(template:Template)->Element:...
Callingstr(element) would then render the HTML but, in the meantime, theElement could be manipulated in a variety of ways.
Continuing with our hypotheticalhtml() function, it could be madecontext-sensitive. Interpolations could be processed differently dependingon where they appear in the template.
For example, ourhtml() function could support multiple kinds ofinterpolations:
attributes={"id":"main"}attribute_value="shrubbery"content="hello"template=t"<div{attributes} data-value={attribute_value}>{content}</div>"element=html(template)assertstr(element)=='<div id="main" data-value="shrubbery">hello</div>'
Because the{attributes} interpolation occurs in the context of an HTML tag,and because there is no corresponding attribute name, it is treated as a dictionaryof attributes. The{attribute_value} interpolation is treated as a simplestring value and is quoted before inclusion in the final string. The{content} interpolation is treated as potentially unsafe content and isescaped before inclusion in the final string.
Going a step further with ourhtml() function, we could support nestedtemplate strings. This would allow for more complex HTML structures to bebuilt up from simpler templates:
name="World"content=html(t"<p>Hello{name}</p>")template=t"<div>{content}</div>"element=html(template)assertstr(element)=='<div><p>Hello World</p></div>'
Because the{content} interpolation is anElement instance, it doesnot need to be escaped before inclusion in the final string.
One could imagine a nice simplification: if thehtml() function is passedaTemplate instance, it could automatically convert it to anElementby recursively calling itself on the nested template.
We expect that nesting and composition of templates will be a common patternin template processing code and, where appropriate, used in preference tosimple string concatenation.
Like f-strings, interpolations in t-string literals are eagerly evaluated. However,there are cases where lazy evaluation may be desirable.
If a single interpolation is expensive to evaluate, it can be explicitly wrappedin alambda in the template string literal:
name="World"template=t"Hello {(lambda: name)}"assertcallable(template.interpolations[0].value)asserttemplate.interpolations[0].value()=="World"
This assumes, of course, that template processing code anticipates and handlescallable interpolation values. (One could imagine also supporting iterators,awaitables, etc.) This is not a requirement of the PEP, but it is a commonpattern in template processing code.
In general, we hope that the community will develop best practices for lazyevaluation of interpolations in template strings and that, when it makes sense,common libraries will provide support for callable or awaitable values intheir template processing code.
Closely related to lazy evaluation is asynchronous evaluation.
As with f-strings, theawait keyword is allowed in interpolations:
asyncdefexample():asyncdefget_name()->str:awaitasyncio.sleep(1)return"Sleepy"template=t"Hello {await get_name()}"# Use the f() function from the f-string example, aboveassertf(template)=="Hello Sleepy"
More sophisticated template processing code can take advantage of this toperform asynchronous operations in interpolations. For example, a “smart”processing function could anticipate that an interpolation is an awaitableand await it before processing the template string:
asyncdefexample():asyncdefget_name()->str:awaitasyncio.sleep(1)return"Sleepy"template=t"Hello{get_name}"assertawaitasync_f(template)=="Hello Sleepy"
This assumes that the template processing code inasync_f() is asynchronousand is able toawait an interpolation’s value.
If developers wish to reuse template strings multiple times with differentvalues, they can write a function to return aTemplate instance:
defreusable(name:str,question:str)->Template:returnt"Hello{name},{question}?"template=reusable("friend","how are you")template=reusable("King Arthur","what is your quest")
This is, of course, no different from how f-strings can be reused.
The venerablestr.format() method accepts format strings that can laterbe used to format values:
alas_fmt="We're all out of{cheese}."assertalas_fmt.format(cheese="Red Leicester")=="We're all out of Red Leicester."
If one squints, one can think of format strings as a kind of function definition.Thecall tostr.format() can be seen as a kind of function call. Thet-string equivalent is to simply define a standard Python function that returnsaTemplate instance:
defmake_template(*,cheese:str)->Template:returnt"We're all out of{cheese}."template=make_template(cheese="Red Leicester")# Using the f() function from the f-string example, aboveassertf(template)=="We're all out of Red Leicester."
Themake_template() function itself can be thought of as analogous to theformat string. The call tomake_template() is analogous to the call tostr.format().
Of course, it is common to load format strings from external sources like afilesystem or database. Thankfully, becauseTemplate andInterpolationare simple Python types, it is possible to write a function that takes anold-style format string and returns an equivalentTemplate instance:
deffrom_format(fmt:str,/,*args:object,**kwargs:object)->Template:"""Parse `fmt` and return a `Template` instance."""...# Load this from a file, database, etc.fmt="We're all out of{cheese}."template=from_format(fmt,cheese="Red Leicester")# Using the f() function from the f-string example, aboveassertf(template)=="We're all out of Red Leicester."
This is a powerful pattern that allows developers to use template strings inplaces where they might have previously used format strings. A full implementationoffrom_format() is available in the examples repository,which supports the full grammar of format strings.
A CPython implementation of PEP 750 isavailable.
There is also a public repository ofexamples and testsbuilt around the reference implementation. If you’re interested in playing withtemplate strings, this repository is a great place to start.
This PEP has been through several significant revisions. In addition, quite a few interestingideas were considered both in revisions ofPEP 501 and in theDiscourse discussion.
We attempt to document the most significant ideas that were considered and rejected.
Inspired byJavaScript tagged template literals,an earlier version of this PEP allowed for arbitrary “tag” prefixes in frontof literal strings:
my_tag'Hello{name}'
The prefix was a special callable called a “tag function”. Tag functionsreceived the parts of the template string in an argument list. They could thenprocess the string and return an arbitrary value:
defmy_tag(*args:str|Interpolation)->Any:...
This approach was rejected for several reasons:
Use of a singlet prefix was chosen as a simpler, more Pythonic approach andmore in keeping with template strings’ role as a generalization of f-strings.
An early version of this PEP proposed that interpolations should be lazilyevaluated. All interpolations were “wrapped” in implicit lambdas. Instead ofhaving an eagerly evaluatedvalue attribute, interpolations had agetvalue() method that would resolve the value of the interpolation:
classInterpolation:..._value:Callable[[],object]defgetvalue(self)->object:returnself._value()
This was rejected for several reasons:
Most importantly, there are viable (if imperfect) alternatives to implicitlambda wrapping in many cases where lazy evaluation is desired. See the sectiononApproaches to Lazy Evaluation, above, for more information.
While delayed evaluation was rejected forthis PEP, we hope that the communitycontinues to explore the idea.
Template andInterpolation Into ProtocolsAn early version of this PEP proposed that theTemplate andInterpolationtypes be runtime checkable protocols rather than classes.
In the end, we felt that using classes was more straightforward.
__eq__ and__hash__ forTemplate andInterpolationEarlier versions of this PEP proposed that theTemplate andInterpolationtypes should have their own implementations of__eq__ and__hash__.
Templates were considered equal if theirstrings andinterpolationswere equal;Interpolations were considered equal if theirvalue,expression,conversion, andformat_spec were equal. Interpolationhashing was similar to tuple hashing: anInterpolation was hashable if andonly if itsvalue was hashable.
This was rejected becauseTemplate.__hash__ so defined was not useful as acache key in template processing code; we were concerned that it would beconfusing to developers.
By dropping these implementations of__eq__ and__hash__, we lose theability to write asserts such as:
name="World"assertt"Hello "+t"{name}"==t"Hello{name}"
BecauseTemplate instances are intended to be quickly processed by furthercode, we felt that the utility of these asserts was limited.
Decoded TypeAn early version of this PEP proposed an additional type,Decoded, to representthe “static string” parts of a template string. This type derived fromstr andhad a single extraraw attribute that provided the original text of the string.We rejected this in favor of the simpler approach of using plainstr andallowing combination ofr andt prefixes.
Template andInterpolationPrevious versions of this PEP proposed placing theTemplate andInterpolation types in:types,collections,collections.abc,and even in a new top-level module,templatelib. The final decision was toplace them instring.templatelib.
Earlier versions of this PEP attempted to make it possible to fully reconstructthe text of the original template string from aTemplate instance. This wasrejected as being overly complex. The mapping between template literal sourceand the underlying AST is not one-to-one and there are several limitations withrespect to round-tripping to the original source text.
First,Interpolation.format_spec defaults to"" if not provided:
value=42template1=t"{value}"template2=t"{value:}"asserttemplate1.interpolations[0].format_spec==""asserttemplate2.interpolations[0].format_spec==""
Next, the debug specifier,=, is treated as a special case and is processedbefore the AST is created. It is therefore not possible to distinguisht"{value=}" fromt"value={value!r}":
value=42template1=t"{value=}"template2=t"value={value!r}"asserttemplate1.strings[0]=="value="asserttemplate1.interpolations[0].expression=="value"asserttemplate1.interpolations[0].conversion=="r"asserttemplate2.strings[0]=="value="asserttemplate2.interpolations[0].expression=="value"asserttemplate2.interpolations[0].conversion=="r"
Finally, format specifiers in f-strings allow arbitrary nesting. In this PEPand in the reference implementation, the specifier is eagerly evaluated toset theformat_spec in theInterpolation, thereby losing the originalexpressions. For example:
value=42precision=2template1=t"{value:.2f}"template2=t"{value:.{precision}f}"asserttemplate1.interpolations[0].format_spec==".2f"asserttemplate2.interpolations[0].format_spec==".2f"
We do not anticipate that these limitations will be a significant issue in practice.Developers who need to obtain the original template string literal can alwaysuseinspect.getsource() or similar tools.
Earlier versions of this PEP proposed thatTemplate instances should notsupport concatenation. This was rejected in favor of allowing concatenatingmultipleTemplate instances.
There are reasonable arguments in favor of rejecting one or all forms ofconcatenation: namely, that it cuts off a class of potential bugs, particularlywhen one takes the view that template strings will often contain complex grammarsfor which concatenation doesn’t always have the same meaning (or any meaning).
Moreover, the earliest versions of this PEP proposed a syntax closer toJavaScript’s tagged template literals, where an arbitrary callable could be usedas a prefix to a string literal. There was no guarantee that the callable wouldreturn a type that supported concatenation.
In the end, we decided that the surprise to developers of a new string typenot supporting concatenation was likely to be greater than the theoreticalharm caused by supporting it.
While concatenation of twoTemplates is supported by this PEP, concatenationof aTemplate and astr is not supported. This is because it isambiguous whetherstr should be treated as a static string or aninterpolation. Developers must wrap thestr in aTemplate instancebefore concatenating it with anotherTemplate, as described above.
We expect that code that uses template strings will more commonly build uplarger templates through nesting and composition rather than concatenation.
Python allows onlyr,s, ora as possible conversion type values.Trying to assign a different value results inSyntaxError.
In theory, template functions could choose to handle other conversion types. But thisPEP adheres closely toPEP 701. Any changes to allowed values should be in aseparate PEP.
conversion FromInterpolationWhile drafting this PEP, we considered removing theconversionattribute fromInterpolation and specifying that the conversion should beperformed eagerly, beforeInterpolation.value is set.
This was done to simplify the work of writing template processing code. Theconversion attribute is of limited extensibility (it is typed asLiteral["r","s","a"]|None). It is not clear that it adds significantvalue or flexibility to template strings that couldn’t better be achieved withcustom format specifiers. Unlike with format specifiers, there is noequivalent to Python’sformat() built-in. (Instead, we include asample implementation ofconvert() in theExamples section.)
Ultimately we decided to keep theconversion attribute in theInterpolation type to maintain compatibility with f-strings and to allowfor future extensibility.
In the early stages of this PEP, we considered allowing alternate symbols forinterpolations in template strings. For example, we considered allowing${name} as an alternative to{name} with the idea that it might be usefulfor i18n or other purposes. See theDiscourse threadfor more information.
This was rejected in favor of keeping t-string syntax as close to f-string syntaxas possible.
TemplateDuring the development of this PEP, we considered several alternate layouts fortheTemplate type. Many focused on a singleargs tuple that containedboth strings and interpolations. Variants included:
args was atuple[str|Interpolation,...]` with the promise thatits first and last items were strings and that strings and interpolationsalways alternated. This implied thatargs was always non-empty and thatempty strings would be inserted between neighboring interpolations. This wasrejected because alternation could not be captured by the type system and wasnot a guarantee we wished to make.args remained atuple[str|Interpolation,...] but did not supportinterleaving. As a result, empty strings were not added to the sequence. Itwas no longer possible to obtain static strings withargs[::2]; instead,instance checks or structural pattern matching had to be used to distinguishbetween strings and interpolations. This approach was rejected as offeringless future opportunity for performance optimization.args was typed as aSequence[tuple[str,Interpolation|None]]. Eachstatic string was paired with is neighboring interpolation. The finalstring part had no corresponding interpolation. This was rejected as beingoverly complex.If t-strings prove popular, it may be useful to have a way to describe the“kind” of content found in a template string: “sql”, “html”, “css”, etc.This could enable powerful new features in tools such as linters, formatters,type checkers, and IDEs. (Imagine, for example,black formatting HTML int-strings, ormypy checking whether a given attribute is valid for an HTMLtag.) While exciting, this PEP does not propose any specific mechanism. It isour hope that, over time, the community will develop conventions for this purpose.
The combination of t-strings and bytes (tb) is considered out of scope forthis PEP. However, unlike f-strings, there is no fundamental reason why t-stringsand bytes cannot be combined. Support could be considered in a future PEP.
Thanks to Ryan Morshead for contributions during development of the ideas leadingto template strings. Special mention also to Dropbox’spyxl for tackling similar ideas years ago.Andrea Giammarchi provided thoughtful feedback on the early drafts of this PEP.Finally, thanks to Joachim Viide for his pioneering work on thetagged library. Tagged was not just the precursor totemplate strings, but the place where the whole effort started via a GitHub issuecomment!
This document is placed in the public domain or under the CC0-1.0-Universallicense, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0750.rst
Last modified:2025-08-17 16:08:51 GMT