Template.get_identifiers / is_valid

Two read-only checks added in 3.11. get_identifiers lists $name and ${name} placeholders (duplicates once) and skips invalid ones; is_valid is False exactly when substitute() would raise ValueError.

Template methodPython 3.11+Live demo
Common call
Template('$a ${b} $a').get_identifiers()
Returns
['a', 'b']
Replaces
re.findall over the template text and try/except around substitute
Watch out
is_valid() says nothing about missing values — only about malformed $
Template.get_identifiers() · Template.is_valid()
→ list[str] · bool

Demo

Live evaluation
The names a template needs, and whether it is well-formed.
Try:
Inputs
textstrtemplate text
Code
from string import Template
t = Template('Dear $title $last, re: ${subject}')
(t.get_identifiers(), t.is_valid())
Result
(['title', 'last', 'subject'], True)

Names come back in the order they first appear, each once, braced or not. $$ is an escaped dollar, not a placeholder, so it neither adds a name nor makes the template invalid. An invalid placeholder ($5, ${ x }) is skipped by get_identifiers and makes is_valid() False — the same templates on which substitute() raises ValueError.

Common patterns

Validate user templates on save
Reject malformed text and unknown names before storing it.
from string import Template
ALLOWED = {'name', 'plan', 'date'}
t = Template(text)
if not t.is_valid():
    raise ValueError('use $$ for a literal dollar sign')
unknown = set(t.get_identifiers()) - ALLOWED
if unknown:
    raise ValueError(f'unknown placeholders: {sorted(unknown)}')
Build the values lazily
Compute only the values the template actually uses.
from string import Template
t = Template(text)
values = {name: providers[name]() for name in t.get_identifiers()}
t.substitute(values)

Examples

1. First-seen order, no duplicates
from string import Template Template('$who $what $who').get_identifiers()
Returns
['who', 'what']
2. Braced and plain together
from string import Template Template('${a}x $b').get_identifiers()
Returns
['a', 'b']
3. Invalid placeholders are skipped
from string import Template Template('cost $5 $x ${y}').get_identifiers()
Returns
['x', 'y']
4. A valid template
from string import Template Template('$who likes $$5').is_valid()
Returns
True
5. A stray $
from string import Template Template('100$').is_valid()
Returns
False
6. Missing values do not matter
from string import Template Template('$anything').is_valid()
Returns
True

Pitfalls

1. Treating is_valid() as "can be filled"
A valid template can still raise KeyError. Check the names too.
is_valid only
from string import Template
t = Template('$name')
t.substitute() if t.is_valid() else None
KeyError: 'name'
names as well
from string import Template
t = Template('$name')
values = {}
t.is_valid() and set(t.get_identifiers()) <= set(values)
False
2. Expecting names the pattern does not allow
A name with a space or a dot is not a placeholder, so get_identifiers silently leaves it out. Widen the pattern in a subclass if such names are intended.
default pattern
from string import Template
Template('${first name} ${last}').get_identifiers()
['last']
braceidpattern
from string import Template
class Spaced(Template):
    braceidpattern = r'(?a:[_a-z][_a-z0-9 ]*)'
Spaced('${first name} ${last}').get_identifiers()
['first name', 'last']

When to use

Use it
  • Validating templates entered in an admin UI or config
  • Finding which values a template needs before computing them
Reach for something else
  • Format strings in {field} syntax → Formatter.parse

Notes

CPython impl
Both iterate self.pattern.finditer(self.template): get_identifiers collects named or braced groups (each once), is_valid returns False at the first invalid group
Version
Added in Python 3.11
Custom patterns
With a custom pattern whose match has none of the four named groups set, both raise ValueError("Unrecognized named group in pattern", pattern)

FAQ

Template(text).get_identifiers() on Python 3.11+ returns the placeholder names in the order they first appear, without duplicates. On older versions, iterate t.pattern.finditer(t.template) and read the 'named' and 'braced' groups.