Introduction to string templates

A string template acts as a sketch of how the final string should look. It is a pattern that is used as a basis for creating other strings. String templates usually includes tags or placeholders that represent a certain set of text that will be replaced with the appropriate value when the actual string is created.

Python offers some various tools for creating string templates some of which you might already be familiar with. They includes:

 %-formatting:

This is a popular formatting style  that uses the '%' operator as a placeholder for variable values.

ExampleEdit & Run
def add(a, b):
   result =  "%s + %s = %s"%(a, b, a + b)
   return result

print(add(10, 20))
Output:
10 + 20 = 30[Finished in 0.011099524097517133s]

As you can see above, the % placeholders gets replaced with the actual values when using the template for creating a string.

str.format():

The str class defines the format() method which takes the passed arguments, formats them, and places them in the string where the {} placeholders are:  

ExampleEdit & Run
S = "This is an example of {}"

print(S.format("string formatting")) 
Output:
This is an example of string formatting[Finished in 0.010367883136495948s]

f-Strings (Formatted String Literals):

This is a relatively new string formatting method in Python (introduced in Python 3.6) that makes use of string literals prefixed with an ‘f’. F-strings provide a simple way to embed expressions inside of string literals.

ExampleEdit & Run
value = "string formatting"

print(f"This is an example of {value}")
Output:
This is an example of string formatting[Finished in 0.010352289071306586s]

The string.Template class

The Template class available  in the string module enables the use of advanced and more expressive way of templating strings. It provides a powerful set of features for customizing how strings are templated, allowing us to perform complex substitutions and formatting operations. 

The string module is freely available in the standard library and we, therefore, just need to import it in our program without extra installations.

ExampleEdit & Run
#import the string module
import string

#Create template string
data = { 'name': 'John Smith', 'age': 33 } 
template = string.Template("${name} is ${age} years old")

print(template.substitute(data))
Output:
John Smith is 33 years old[Finished in 0.017027969006448984s]

Basic Syntax

Constructing the template:

We start by creating an instance of the Template class and passing in a string containing placeholders By convention we enclose the placeholders in curly braces ({}) in order tp distinguish them from other words in the template string.

Syntax:
Template(template)
template A required string that containing the necessary placeholders. 
**kwargs Any other necessary keyword arguments to be used in the formating such as delimiter
ExampleEdit & Run
import string

my_template = string.Template("your name is ${name} and your age is ${age}")
print(my_template.template)
Output:
your name is ${name} and your age is ${age}[Finished in 0.017054964788258076s]

By default, the Template class uses the dollar sign("$") character as the placeholder delimiter but we can change this as we will see later in Advanced templates

Substituting a value into the template

This is done by calling the substitute() method on the template string, and passing in a dictionary with the desired values, or alternatively, passing the substitute values as keyword arguments.

ExampleEdit & Run
import string

#create the template
my_template = string.Template("your name is ${name} and your age is ${age}")

#perform the substitution
final_string = my_template.substitute(name= "John Doe", age = 30)

print(final_string)
Output:
your name is John Doe and your age is 30[Finished in 0.017253831028938293s]

The substitute() method have the following syntax:

Syntax:
substitute(mapping={}, **kwargss)

Where, mapping is an optional argument which is given if we are not passing the substitute values as keyword argument. Likewise, the **kwargs arguments refers to the substitute values passes as keyword arguments if we are not passing them in the mapping dictionary. 

ExampleEdit & Run
import string 

data = {"name": "John Doe", "city": "New York"} 
# Create a string template
template = string.Template("${name} lives in ${city}") 

# Use substitute to replace placeholders with actual values 
substituted_string = template.substitute(data)

print(substituted_string)
Output:
John Doe lives in New York[Finished in 0.01634631911292672s]

 A practical example

ExampleEdit & Run
import string

def evaluate(a, b, oper="+"):
     output_template = string.Template("${a} ${oper} ${b} = ${result}")
     
     result = None
     match oper:
         case "+":
             result = a + b
         case "-":
             result = a - b
         case "x":
             result = a * b
         case "/":
             result = a / b
         case _:
             return "Invalid operator"
     return output_template.substitute({"a": a , "b": b, "oper": oper, "result": result})

print(evaluate(20, 10))
print(evaluate(20, 10, "-"))
print(evaluate(20, 10, "x"))
print(evaluate(20, 10, "/"))
Output:
20 + 10 = 3020 - 10 = 1020 x 10 = 20020 / 10 = 2.0[Finished in 0.01653761020861566s]

Safe Substitution

The substitute() method will raise a KeyError exception if we do not provide any of the defined substitute values.

ExampleEdit & Run
import string

template_string = "${a}, ${b}"

template = string.Template(template_string)

template.substitute(a = 2)
Output:
Traceback (most recent call last):  File "<string>", line 7, in <module>  File "/app/.heroku/python/lib/python3.11/string.py", line 121, in substitute    return self.pattern.sub(convert, self.template)           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  File "/app/.heroku/python/lib/python3.11/string.py", line 114, in convert    return str(mapping[named])               ~~~~~~~^^^^^^^KeyError: 'b'[Finished in 0.017228598007932305s]

We can avoid the error with safe substitution using the safe_substitute() method. This method replaces all placeholders in the template string, but will leave unknown placeholders intact. This makes it possible to use templates even when not all possible placeholders exist.

ExampleEdit & Run
import string

data = {"name": "John Doe", "age": 30, "country": "Finland"}

template_string = "My name is ${name}, I am ${age} years old, I live in ${country} and I am married to ${spouse}"

my_template = string.Template(template_string)

print(my_template.safe_substitute(data))
Output:
My name is John Doe, I am 30 years old, I live in Finland and I am married to ${spouse}[Finished in 0.01632766891270876s]

Get all the placeholder names

The get_identifiers() method returns a list containing all the placeholder names used in the template string.

ExampleEdit & Run
import string

template_string = "My name is ${name}, I am ${age} years old, I live in ${country} and I am married to ${spouse}"

my_template = string.Template(template_string)

print(my_template.get_identifiers())
Output:
['name', 'age', 'country', 'spouse'][Finished in 0.016835237154737115s]

Advanced templates

The default way that the string.Template class works can be overridden by adjusting the  the regex pattern that it uses to find the placeholder names in the template string. A direct way to achieve this is by simply changing the delimiter and  idpattern class attributes.

ExampleEdit & Run
import string

template_string = """
name : @_name
age: @_age
country: @_country
spouse: @_spouse
"""

data = {"_name": "John Doe", "_age": 30, "_country": "Finland", "_spouse": "Mary Doe"} 

class MyTemplate(string.Template):
     delimiter = "@"
     idpattern = "_[a-z]+"

my_template = MyTemplate(template_string)

final_string = my_template.substitute(data)
print(final_string)
Output:
name : John Doeage: 30country: Finlandspouse: Mary Doe[Finished in 0.021332926815375686s]

In the above example, substitution rules are changed so that the delimiter is '@' instead of the default  '$' and placeholder names must start with an underscore.

You can define more complex rules by setting an entirely new regular expressions as the idpattern.