Leaving values blank if not passed in str.format

六眼飞鱼酱① 提交于 2019-11-27 14:20:24
dawg

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}.

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.

For the record:

s = "My name is {name} and I'm really {adjective}."
kwargs = dict((x[1], '') for x in s._formatter_parser())
# Now we have: `kwargs = {'name':'', 'adjective':''}`.
kwargs.update(name='mark')
print s.format(**kwargs)  # My name is mark and I'm really .

Wanted to add a pretty simple solution to the substituting any default values needed.

import string

class SafeDict(dict):
    def __init__(self, missing='#', empty='', *args, **kwargs):
        super(SafeDict, self).__init__(*args, **kwargs)
        self.missing = missing
        self.empty = empty
    def __getitem__(self, item):
        return super(SafeDict, self).__getitem__(item) or self.empty
    def __missing__(self, key):
        return self.missing

values = SafeDict(a=None, c=1})
string.Formatter().vformat('{a} {c} {d}', (), values)
# ' 1 #'

While subclassing a Formatter is probably the "right" answer, it is also possible to follow Python's strong ask-for-forgiveness-not-permission vein by catching the KeyError. The advantage of this approach is that it is easily flexible: In particular, it is easy to have "default" values which are not static (i.e., just a possibly blank constant) but can depend on the name of the key, such as here:

def f(s, **kwargs):
    """Replaces missing keys with a pattern."""
    RET = "{{{}}}"
    try:
        return s.format(**kwargs)
    except KeyError as e:
        keyname = e.args[0]
        return f(s, **{ keyname: RET.format(keyname) }, **kwargs)

which will work in the following way:

In [1]: f("My name is {name} and I'm really {adjective}.", **{"name": "Mark"})
Out[1]: "My name is Mark and I'm really {adjective}."

This can be specialized easily to do what the OP wanted:

def f_blank(s, **kwargs):
    """Replaces missing keys with a blank."""
    try:
        return s.format(**kwargs)
    except KeyError as e:
        keyname = e.args[0]
        return f(s, **{ keyname: "" }, **kwargs)

I had a bit more fun with this idea: https://gist.github.com/jlumbroso/57951c06a233c788e00d0fc309a93f91

# (not a real import! just saying importing the code from the Gist)
from gist.57951c06a233c788e00d0fc309a93f91 import _make_f

# Define replacement f"..." compatible with Python 2 and 3
_f = _make_f(globals=lambda: globals(), locals=lambda: locals())

# Use:
s = "Test"
var = 1
assert _f("{s} {var}") == "Test 1"

# Inside a non-global scope, you may have to provide locals
def test():
    l_s = "Test"
    l_var = 1
    assert _f("{l_s} {l_var} / {s} {var}") == "{l_s} {l_var} / Test 1"
    assert _f("{l_s} {l_var} / {s} {var}", **locals()) == "Test 1 / Test 1"

If you are still on Python 2, you can use defaultdict with string.Formatter to achieve this:

>>> format_string = '{foo:<2s}{bar:<3s}'
>>> data = {'bar': 'baz'}
>>> string.Formatter().vformat(format_string, (), defaultdict(str, data))
'  baz'

A way to avoid the key error is to include in the dict but leave it blank:

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

Keyword arguments expect their to be a key in kwargs. Another way to do it would be positional arguments:

"My name is {0} and I'm really {1}.".format("mark")

Prints "My name is Mark and I'm really." While

"My name is {0} and I'm really {1}.".format("mark","black")

Prints "My name is Mark and I'm really black."

Alternatively, you can catch the ValueError.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!