问题
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.
回答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}.
回答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.
回答3:
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 .
回答4:
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 #'
回答5:
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"
回答6:
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'
回答7:
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.
来源:https://stackoverflow.com/questions/19799609/leaving-values-blank-if-not-passed-in-str-format