Template.delimiter / idpattern / braceidpattern / flags / pattern

Set them in the body of a subclass. When the class is created, __init_subclass__ compiles delimiter, idpattern, braceidpattern and flags into the class attribute pattern — so changing them later on the class has no effect.

Template attributesPython 2.4+ (flags 3.2+, braceidpattern 3.7+)Live demo
Common call
class Percent(Template): delimiter = '%'
Returns
Percent('%name').substitute(name='x') → 'x'
Replaces
search-and-replace with a home-made regex
Watch out
set them in the class body; assigning T.delimiter afterwards is ignored
class T(Template)
→ str · str · str | None · re.RegexFlag · re.Pattern

Demo

Live evaluation
A subclass that uses % instead of $. %% is now the literal, and $ is ordinary text.
Try:
Inputs
textstrtemplate using %name
namestrvalue for name
Code
from string import Template
class Percent(Template):
    delimiter = '%'
Percent('Hi %name, 100%% sure').safe_substitute(name='Ada')
Result
'Hi Ada, 100% sure'

With delimiter '%', '%%' is the escaped literal and a '%' before a space is an invalid placeholder, which safe_substitute leaves alone. Allowing '.' in idpattern has a cost: the name now swallows a sentence-ending dot, so 'Bye $user.name.' looks up 'user.name.', which does not exist — use ${user.name} at the end of a sentence.

Attributes

AttributeTypeMeaning
delimiterstrThe literal text that starts a placeholder; default '$'. Escaped with re.escape, so it is not a regex. Doubling it gives a literal.
idpatternstrRegex for names after the delimiter; default '(?a:[_a-z][_a-z0-9]*)'. Also used inside braces unless braceidpattern is set.
braceidpatternstr | NoneRegex for names inside {braces}; default None = use idpattern (3.7+).
flagsre.RegexFlagFlags for compiling; default re.IGNORECASE. re.VERBOSE is always added, so whitespace in your patterns is ignored (3.2+).
patternre.PatternThe compiled regex with groups escaped, named, braced and invalid. Override it to replace the syntax completely.

Common patterns

Different delimiter
Handy when the text is full of dollar signs (prices, shell snippets).
from string import Template

class AtTemplate(Template):
    delimiter = '@'

AtTemplate('Total: $5 for @name').substitute(name='Ada')
Case-sensitive upper-case names
Only $UPPER names are placeholders; drop IGNORECASE so lower-case text is left alone.
from string import Template

class EnvTemplate(Template):
    idpattern = r'[A-Z_][A-Z0-9_]*'
    flags = 0

EnvTemplate('$HOME is not $home').safe_substitute(HOME='/root')
A completely different syntax
Override pattern with the four named groups — here {{name}} placeholders.
from string import Template

class Mustache(Template):
    pattern = r'''\{\{(?:
        (?P<escaped>\{\{) |
        (?P<named>[a-z]+)\}\} |
        (?P<braced>[a-z]+)\}\} |
        (?P<invalid>)
    )'''

Mustache('Hi {{name}}!').substitute(name='Ada')

Examples

1. The defaults
from string import Template Template.delimiter, Template.idpattern, Template.braceidpattern
Returns
('$', '(?a:[_a-z][_a-z0-9]*)', None)
2. Default flags
from string import Template Template.flags
Returns
re.IGNORECASE
3. pattern is compiled
from string import Template type(Template.pattern).__name__
Returns
'Pattern'
4. IGNORECASE: lower case matches too
from string import Template class Upper(Template): idpattern = r'[A-Z]+' Upper('$NAME $name').safe_substitute(NAME='Ada', name='x')
Returns
'Ada x'
5. flags = 0 makes it strict
from string import Template class Upper(Template): idpattern = r'[A-Z]+' flags = 0 Upper('$NAME $name').safe_substitute(NAME='Ada', name='x')
Returns
'Ada $name'
6. braceidpattern only inside braces
from string import Template class Spaced(Template): braceidpattern = r'(?a:[_a-z][_a-z0-9 ]*)' Spaced('${first name} $first').substitute({'first name': 'Ada', 'first': 'A'})
Returns
'Ada A'
7. A custom pattern
from string import Template class Mustache(Template): pattern = r'''\{\{(?: (?P<escaped>\{\{) | (?P<named>[a-z]+)\}\} | (?P<braced>[a-z]+)\}\} | (?P<invalid>) )''' Mustache('Hi {{name}}!').substitute(name='Ada')
Returns
'Hi Ada!'

Pitfalls

1. Changing the delimiter after the class exists
pattern is compiled once, when the class is created. Assigning the attribute later changes nothing.
assign later
from string import Template
class Late(Template):
    pass
Late.delimiter = '%'
Late('%x $x').safe_substitute(x=1)
'%x 1'
class body
from string import Template
class Early(Template):
    delimiter = '%'
Early('%x $x').safe_substitute(x=1)
'1 $x'
2. Dropping the (?a:) ASCII scope
With IGNORECASE but without the ASCII flag, [a-z] also matches a few non-ASCII letters — such as the Kelvin sign (U+212A) and the long s ſ — which then become part of names.
no (?a:)
from string import Template
class Loose(Template):
    idpattern = r'[_a-z][_a-z0-9]*'
Loose('$\u212a').get_identifiers()
['K']
keep (?a:)
from string import Template
Template('$\u212a').get_identifiers()
[]

When to use

Use it
  • Text that naturally contains $ (prices, shell code) — another delimiter
  • Names with dots, dashes or upper-case conventions — idpattern / braceidpattern / flags
  • A different placeholder syntax altogether — pattern
Reach for something else
  • Logic in templates (conditions, loops) → a template engine

Notes

CPython impl
Template.__init_subclass__ builds pattern = delimiter (re.escape'd) + (?: (?P<escaped>delim) | (?P<named>idpattern) | {(?P<braced>braceidpattern or idpattern)} | (?P<invalid>) ) and compiles it with flags | re.VERBOSE
VERBOSE
Because re.VERBOSE is always added, a literal space in idpattern must be escaped or put in a character class
pattern
A pattern you set yourself may be a str or an already compiled re.Pattern; it must define the groups escaped, named, braced and invalid
Instance attribute
template — the text passed to the constructor; documented, but not meant to be changed

FAQ

Subclass it and set delimiter in the class body: class P(Template): delimiter = '%'. Doubling the new delimiter (%%) gives a literal one, and $ becomes ordinary text.