Yann
Yann

Reputation: 35493

Get keys from template

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

Answers (8)

Jason Kohles
Jason Kohles

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

Brandon LeBlanc
Brandon LeBlanc

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

Roman Imankulov
Roman Imankulov

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

Eric
Eric

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

Ashwini Chaudhary
Ashwini Chaudhary

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

volcano
volcano

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

che
che

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

dckrooney
dckrooney

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

Related Questions