First if you want to match a template string don't use the f'' string prefix or else it will be immediately evaluated. Instead just write the format string like:
sample = 'This is a {pet} it has {number} legs'
Here's a function I wrote for one project for parsing a format string and converting it to a regular expression:
import re
import string
def format_to_re(format_str, **kwargs):
r"""
Convert a format string to a regular expression, such that any format
fields may replaced with regular expression syntax, and any literals are
properly escaped.
As a special case, if a 2-tuple is given for the value of a field, the
first time the field appears in the format string the first element of the
tuple is used as the replacement, and the second element is used for all
subsequence replacements.
Examples
--------
This example uses a backslash just to add a little Windows flavor:
>>> filename_format = \
... r'scenario_{scenario}\{name}_{scenario}_{replicate}.npz'
>>> filename_re = format_to_re(filename_format,
... scenario=(r'(?P<scenario>0*\d+)', r'0*\d+'),
... replicate=r'0*\d+', name=r'\w+')
>>> filename_re
'scenario_(?P<scenario>0*\\d+)\\\\\\w+_0*\\d+_0*\\d+\\.npz'
>>> import re
>>> filename_re = re.compile(filename_re)
>>> filename_re
re.compile(...)
This regular expression can be used to match arbitrary filenames to
determine whether or not they are in the format specified by the original
``filename_format`` template, as well as to extract the values of fields by
using groups:
>>> match = filename_re.match(r'scenario_000\my_model_000_000.npz')
>>> match is not None
True
>>> match.group('scenario')
'000'
>>> filename_re.match(r'scenario_000\my_model_garbage.npz') is None
True
"""
formatter = string.Formatter()
new_format = []
seen_fields = set()
for item in formatter.parse(format_str):
literal, field_name, spec, converter = item
new_format.append(re.escape(literal))
if field_name is None:
continue
replacement = kwargs[field_name]
if isinstance(replacement, tuple) and len(replacement) == 2:
if field_name in seen_fields:
replacement = replacement[1]
else:
replacement = replacement[0]
new_format.append(replacement)
seen_fields.add(field_name)
return ''.join(new_format)
You can use this on your example like:
>>> sample_re = format_to_re(sample, pet=r'(?P<pet>.+)', number=r'(?P<number>\d+)')
>>> sample_re = re.compile(sample_re)
>>> sample_re
re.compile('This\\ is\\ a\\ (?P<pet>.+)\\ it\\ has\\ (?P<number>\\d+)\\ legs')
>>> m = sample_re.match('This is a dog it has 4 legs')
>>> m.groupdict()
{'pet': 'dog', 'number': '4'}
Depending on your use case you may be able to simplify it a bit. The original version was to handle some application-specific cases.
Another possible enhancement is, given an arbitrary format string, provide default regexps for each field found in it, possibly determined by any format specifiers in the field.