Skip to content. | Skip to navigation

Personal tools
You are here: Home TBSI Technology Blog Topics Python

Python

Mar 28, 2011

Python: How to tell if a format string references a given variable

by Eric Smith — last modified Mar 29, 2011 09:15 AM
Filed Under:

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