How to get Python to gracefully format None and non-existing fields

Juan A. Navarro picture Juan A. Navarro · Nov 27, 2013 · Viewed 28.9k times · Source

If I write in Python:

data = {'n': 3, 'k': 3.141594, 'p': {'a': 7, 'b': 8}}
print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))
del data['k']
data['p']['b'] = None
print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))

I get:

3, 3.14, 7, 8
Traceback (most recent call last):
  File "./funky.py", line 186, in <module>
    print('{n}, {k:.2f}, {p[a]}, {p[b]}'.format(**data))
KeyError: 'k'

Instead of an error message, how can I get Python to more gracefully format the None's and non existent fields?

To give an example, I would like to see in the output something more like:

3, 3.14, 7, 8
3, ~, 7, ~

Ideally, of course, I would like to be able to specify the string used instead of those missing values.

Answer

dawg picture dawg · Nov 27, 2013

The recommendation in PEP 3101 is to subclass Formatter:

import string
class PartialFormatter(string.Formatter):
    def __init__(self, missing='~~', bad_fmt='!!'):
        self.missing, self.bad_fmt=missing, bad_fmt

    def get_field(self, field_name, args, kwargs):
        # Handle a key not found
        try:
            val=super(PartialFormatter, self).get_field(field_name, args, kwargs)
            # Python 3, 'super().get_field(field_name, args, kwargs)' works
        except (KeyError, AttributeError):
            val=None,field_name 
        return val 

    def format_field(self, value, spec):
        # handle an invalid format
        if value==None: return self.missing
        try:
            return super(PartialFormatter, self).format_field(value, spec)
        except ValueError:
            if self.bad_fmt is not None: return self.bad_fmt   
            else: raise

fmt=PartialFormatter()
data = {'n': 3, 'k': 3.141594, 'p': {'a': '7', 'b': 8}}
print(fmt.format('{n}, {k:.2f}, {p[a]}, {p[b]}', **data))
# 3, 3.14, 7, 8
del data['k']
data['p']['b'] = None
print(fmt.format('{n}, {k:.2f}, {p[a]:.2f}, {p[b]}', **data))
# 3, ~~, !!, ~~

As set up, it will print ~~ if a field or attribute is not found and !! if an invalid format is used given the field value. (Just use None for the keyword argument bad_fmt if you want the default of a value error raised.)

To handle missing keys, you need to subclass both get_field to catch the KeyError or AttributeError and format_field to return a default value for the missing key.

Since you are catching format_field errors, you can catch a bad format field as well by catching the ValueError from the superclass.