Python: How to tell if a format string references a given variable
A friend recently asked how I would determine if a given variable was used in a format string. Here's my solution.
Python 2.6 and greater support str.format(), also known as PEP-3101 Advanced String Formatting. A friend asked me how, given a string to be formatted, he could tell if a variable was refernced by that string and would be expanded.
As he pointed out, this is easy enough if you're using Python 3.2 and have access to str.format_map(). You'd just write a special dict that would remember what keys have been accessed. But before Python 3.2, you're stuck with no good answer.
Given that I wrote the implementation of str.format(), I thought I'd take a crack at this.
My solution was to use string.Formatter. This is a little-known (and even less used) class that exposes the internals of str.format(), but all wrapped up in a Python class with a number of overridable methods. Here was my first attempt:
import string class Formatter(string.Formatter): def __init__(self): self.used = set() def get_value(self, key, args, kwargs): self.used.add(key) return '' def is_used(var, format_string): formatter = Formatter() formatter.format(format_string) return var in formatter.used
Given that, here's how you use it:
>>> is_used('foo', '{foo}')
True
>>> is_used('foo1', '{foo}')
False
>>> is_used('foo', '{{foo}}')
False
However, there's a problem. If you have a format specifier that doesn't make sense for a string, you'll get an exception:
>>> is_used('date', '{date:%Y-%m-%d}')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in is_used
File "C:\Python26\lib\string.py", line 543, in format
return self.vformat(format_string, args, kwargs)
File "C:\Python26\lib\string.py", line 547, in vformat
result = self._vformat(format_string, args, kwargs, used_args, 2)
File "C:\Python26\lib\string.py", line 580, in _vformat
result.append(self.format_field(obj, format_spec))
File "C:\Python26\lib\string.py", line 597, in format_field
return format(value, format_spec)
ValueError: Invalid conversion specification
My solution was to add another class that ignores any format specifier:
import string class AnyFormatSpec: def __format__(self, fmt): return '' class Formatter(string.Formatter): def __init__(self): self.used = set() def get_value(self, key, args, kwargs): self.used.add(key) return AnyFormatSpec() def is_used(var, format_string): formatter = Formatter() formatter.format(format_string) return var in formatter.used
Now we can check for any variable, with any format specifier:
>>> is_used('date', '{date:%Y-%m-%d}')
True
>>> is_used('pi', '{pi:.12f}')
True
>>> is_used('pi', '{pie}')
False

