Leaving values blank if not passed in str.format

10,748

Solution 1

You can follow the recommendation in PEP 3101 and use a subclass Formatter:

import string

class BlankFormatter(string.Formatter):
    def __init__(self, default=''):
        self.default=default

    def get_value(self, key, args, kwds):
        if isinstance(key, str):
            return kwds.get(key, self.default)
        else:
            return string.Formatter.get_value(key, args, kwds)

kwargs = {"name": "mark", "adj": "mad"}     
fmt=BlankFormatter()
print fmt.format("My name is {name} and I'm really {adj}.", **kwargs)
# My name is mark and I'm really mad.
print fmt.format("My name is {name} and I'm really {adjective}.", **kwargs)
# My name is mark and I'm really .  

As of Python 3.2, you can use .format_map as an alternative:

class Default(dict):
    def __missing__(self, key):
        return '{'+key+'}'

kwargs = {"name": "mark"}

print("My name is {name} and I'm really {adjective}.".format_map(Default(kwargs)))

which prints:

My name is mark and I'm really {adjective}.

Solution 2

Here is one option which uses collections.defaultdict:

>>> from collections import defaultdict
>>> kwargs = {"name": "mark"}
>>> template = "My name is {0[name]} and I'm really {0[adjective]}."
>>> template.format(defaultdict(str, kwargs))
"My name is mark and I'm really ."

Note that we aren't using ** to unpack the dictionary into keyword arguments anymore, and the format specifier uses {0[name]} and {0[adjective]}, which indicates that we should perform a key lookup on the first argument to format() using "name" and "adjective" respectively. By using defaultdict a missing key will result in an empty string instead of raising a KeyError.

Share:
10,748

Related videos on Youtube

marky1991
Author by

marky1991

Updated on June 04, 2022

Comments

  • marky1991
    marky1991 almost 2 years

    I've run into a fairly simple issue that I can't come up with an elegant solution for.

    I'm creating a string using str.format in a function that is passed in a dict of substitutions to use for the format. I want to create the string and format it with the values if they're passed and leave them blank otherwise.

    Ex

    kwargs = {"name": "mark"}
    "My name is {name} and I'm really {adjective}.".format(**kwargs)
    

    should return

    "My name is mark and I'm really ."
    

    instead of throwing a KeyError (Which is what would happen if we don't do anything).

    Embarrassingly, I can't even come up with an inelegant solution for this problem. I guess I could solve this by just not using str.format, but I'd rather use the built-in (which mostly does what I want) if possible.

    Note: I don't know in advance what keys will be used. I'm trying to fail gracefully if someone includes a key but doesn't put it in the kwargs dict. If I knew with 100% accuracy what keys would be looked up, I'd just populate all of them and be done with it.

  • marky1991
    marky1991 over 10 years
    The indexing solution isn't (aesthetically) pretty, but you're right, it works as I want.
  • marky1991
    marky1991 over 10 years
    Hmm. This is interesting. I think I'll actually go this route. I'm still not a fan of the ugly indexing that the other answer uses. Thanks for the suggestion!
  • Elias Zamaria
    Elias Zamaria over 7 years
    I am a bit confused. Shouldn't Formatter be string.Formatter? Can that else part even be reached?
  • dawg
    dawg about 7 years
    @EliasZamaria: Yes, it should have been return string.Formatter and that has been corrected. The else is reached if you use tuple indexing formatting like "one {0} two {1}".format(1,2) instead of keyword access. Thanks for the comment. Three year old bug fixed!
  • IvanD
    IvanD over 5 years
    This is the simplest solution that I've found. It can also be a one-liner: "My name is {0[name]} and I'm really {0[adjective]}.".format(defaultdict(str, kwargs)) Beautiful!