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.
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
| Attribute | Type | Meaning |
|---|---|---|
| delimiter | str | The literal text that starts a placeholder; default '$'. Escaped with re.escape, so it is not a regex. Doubling it gives a literal. |
| idpattern | str | Regex for names after the delimiter; default '(?a:[_a-z][_a-z0-9]*)'. Also used inside braces unless braceidpattern is set. |
| braceidpattern | str | None | Regex for names inside {braces}; default None = use idpattern (3.7+). |
| flags | re.RegexFlag | Flags for compiling; default re.IGNORECASE. re.VERBOSE is always added, so whitespace in your patterns is ignored (3.2+). |
| pattern | re.Pattern | The 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.IGNORECASE3. 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.