Reputation: 35493
I would like to get a list of all possible keyword arguments a string template might use in a substitution.
Is there a way to do this other than re?
I want to do something like this:
text="$one is a $lonely $number."
keys = get_keys(text)
# keys = ('one', 'lonely', 'number')
I'm writing a simple Mad-lib-like program, and I want to perform template substitution with either string.format or Template strings. I'd like to write the 'story' and have my program produce a template file of all the 'keywords' (nouns, verbs, etc.) that a user would need to produce. I know I can do this with regular expressions, but I was wondering if there is an alternative solution? I'm open to alternatives to string.format and string template.
I thought there would be solution to this, but I haven't come across it in a quick search. I did find this question, reverse template with python, but it's not really what I'm looking for. It just reaffirms that this can be done with re
.
EDIT:
I should note that $$
is an escape for '$', and is not a token I want. $$5
should render to "$5".
Upvotes: 19
Views: 9057
Reputation: 804
Starting with Python 3.11, string.Template
has a method to do this easily.
>>> from string import Template
>>> Template( 'Some ${thing} with $placeholders' ).get_identifiers()
['thing', 'placeholders']
Upvotes: 1
Reputation: 580
The string.Template
class has the pattern that is uses as an attribute. You can print the pattern to get the matching groups
>>> print string.Template.pattern.pattern
\$(?:
(?P<escaped>\$) | # Escape sequence of two delimiters
(?P<named>[_a-z][_a-z0-9]*) | # delimiter and a Python identifier
{(?P<braced>[_a-z][_a-z0-9]*)} | # delimiter and a braced identifier
(?P<invalid>) # Other ill-formed delimiter exprs
)
And for your example,
>>> string.Template.pattern.findall("$one is a $lonely $number.")
[('', 'one', '', ''), ('', 'lonely', '', ''), ('', 'number', '', '')]
As you can see above, if you do ${one}
with braces it will go to the third place of the resulting tuple:
>>> string.Template.pattern.findall('${one} is a $lonely $number.')
[('', '', 'one', ''), ('', 'lonely', '', ''), ('', 'number', '', '')]
So if you want to get all the keys, you'll have to do something like:
>>> [s[1] or s[2] for s in string.Template.pattern.findall('${one} is a $lonely $number.$$') if s[1] or s[2]]
['one', 'lonely', 'number']
Upvotes: 13
Reputation: 8817
If it's okay to use string.format
, consider using built-in class string.Formatter
which has a parse()
method:
>>> from string import Formatter
>>> [i[1] for i in Formatter().parse('Hello {1} {foo}') if i[1] is not None]
['1', 'foo']
See here for more details.
Upvotes: 43
Reputation: 3172
You could render it once with an instrumented dictionary which records calls, or a defaultdict, and then check what it asked for.
from collections import defaultdict
d = defaultdict("bogus")
text%d
keys = d.keys()
Upvotes: 4
Reputation: 250911
try str.strip()
along with str.split()
:
In [54]: import string
In [55]: text="$one is a $lonely $number."
In [56]: [x.strip(string.punctuation) for x in text.split() if x.startswith("$")]
Out[56]: ['one', 'lonely', 'number']
Upvotes: 1
Reputation: 3582
>>> import string
>>> get_keys = lambda s:[el.strip(string.punctuation)
for el in s.split()if el.startswith('$')]
>>> get_keys("$one is a $lonely $number.")
['one', 'lonely', 'number']
Upvotes: 0
Reputation: 12263
Why do you want to avoid regular expressions? They work quite well for this:
>>> re.findall(r'\$[a-z]+', "$one is a $lonely $number.")
['$one', '$lonely', '$number']
For templating, check out re.sub, it can be called with callback to do almost the thing you want.
Upvotes: 1
Reputation: 3121
You could try:
def get_keys(s):
tokens = filter(lambda x: x[0] == "$", s.split())
return map(lambda x: x[1:], tokens)
Upvotes: 0