Python字符串格式化抑制/静默KeyError/IndexError
有没有办法让python的string.format在缺少索引时不抛出异常,而是插入一个空字符串呢?
result = "i am an {error} example string {error2}".format(hello=2,error2="success")
这里,结果应该是:
"i am an example string success"
现在,python会抛出一个keyerror,并停止格式化。有没有办法改变这种行为呢?
谢谢
编辑:
有一个Template.safe_substitute(即使这样也会保留模式,而不是插入空字符串),但string.format难道不能有类似的功能吗?
希望的行为类似于php中的字符串替换。
class Formatter(string.Formatter):
def get_value(self,key,args,kwargs):
try:
if hasattr(key,"__mod__"):
return args[key]
else:
return kwargs[key]
except:
return ""
这似乎提供了想要的行为。
4 个回答
很遗憾,默认情况下是没有这种方法的。不过,你可以使用一个叫做 defaultdict 的东西,或者一个重写了 __getattr__
的对象,然后像这样使用:
class SafeFormat(object):
def __init__(self, **kw):
self.__dict = kw
def __getattr__(self, name):
if not name.startswith('__'):
return self.__dict.get(name, '')
print "i am an {0.error} example string {0.error2}".format(SafeFormat(hello=2,error2="success"))
i am an example string success
str.format()
这个方法不支持映射对象。你可以试试这样做:
from collections import defaultdict
d = defaultdict(str)
d['error2'] = "success"
s = "i am an {0[error]} example string {0[error2]}"
print s.format(d)
你可以创建一个默认字典(defaultdict),它的工厂函数是 str()
,这样它会返回一个空字符串 ""。然后,你可以为这个默认字典创建一个键。在格式字符串中,你可以访问第一个传入对象的键。这种方法的好处是,只要你的默认字典是 format()
的第一个参数,你就可以传入其他的键和值。
另外,可以查看这个链接:http://bugs.python.org/issue6081
官方的解决方案是通过创建一个字典的子类,并定义一个特殊的方法 __missing__()
来处理格式化字符串中的缺失键。这个方法会在找不到某个键时被调用,它返回的内容会用来替代格式化字符串中的占位符:
class format_dict(dict):
def __missing__(self, key):
return "..."
d = format_dict({"foo": "name"})
print("My %(foo)s is %(bar)s" % d) # "My name is ..."
print("My {foo} is {bar}".format(**d)) # "My name is ..."
补充:第二个 print() 在 Python 3.5.3 中可以正常工作,但在比如 3.7.2 中就不行了:会抛出 KeyError: 'bar'
错误,我也没找到处理这个错误的方法。
经过一些实验,我发现 Python 的行为有些不同。在 3.5.3 版本中,调用的是 __getitem__(self, "foo")
,这个调用成功了,而 __getitem__(self, "bar")
找不到键 "bar"
,所以它会调用 __missing__(self, "bar")
来处理这个缺失的键,而不会抛出 KeyError。在 3.7.2 版本中,内部调用了 __getattribute__(self, "keys")
。内置的 keys()
方法用来返回一个键的迭代器,结果是返回了 "foo",然后 __getitem__("foo")
成功了,但迭代器已经用完了。对于格式字符串中的 {bar}
,没有找到键 "bar"
。因此 __getitem__()
和 __missing_()
都没有被调用来处理这个情况。相反,抛出了 KeyError。我不知道该如何捕获这个错误,如果可以的话。
在 Python 3.2 及以上版本中,你应该使用 format_map()
方法(也可以查看 Python Bug Tracker - Issue 6081):
from collections import defaultdict
d = defaultdict(lambda: "...")
d.update({"foo": "name"})
print("My {foo} is {bar}".format_map(d)) # "My name is ..."
如果你想保留占位符,可以这样做:
class Default(dict):
def __missing__(self, key):
return key.join("{}")
d = Default({"foo": "name"})
print("My {foo} is {bar}".format_map(d)) # "My name is {bar}"
如你所见,format_map()
确实会调用 __missing__()
。
以下方法似乎是最兼容的解决方案,因为它在包括 2.x 的旧版本 Python 中也能工作(我测试了 v2.7.15):
class Default(dict):
def __missing__(self, key):
return key.join("{}")
d = Default({"foo": "name"})
import string
print(string.Formatter().vformat("My {foo} is {bar}", (), d)) # "My name is {bar}"
要保留占位符的原样包括格式说明符(例如 {bar:<15}
),需要对 Formatter 进行子类化:
import string
class Unformatted:
def __init__(self, key):
self.key = key
def __format__(self, format_spec):
return "{{{}{}}}".format(self.key, ":" + format_spec if format_spec else "")
class Formatter(string.Formatter):
def get_value(self, key, args, kwargs):
if isinstance(key, int):
try:
return args[key]
except IndexError:
return Unformatted(key)
else:
try:
return kwargs[key]
except KeyError:
return Unformatted(key)
f = Formatter()
s1 = f.vformat("My {0} {1} {foo:<10} is {bar:<15}!", ["real"], {"foo": "name"})
s2 = f.vformat(s1, [None, "actual"], {"bar":"Geraldine"})
print(s1) # "My real {1} name is {bar:<15}!"
print(s2) # "My real actual name is Geraldine !"
注意,占位符的索引没有改变({1}
在字符串中保持不变,没有 {0}
),而且为了替换 {1}
,你需要传递一个数组,数组的第一个元素是任意的奇数,第二个元素是你想替换剩余占位符的内容(例如 [None, "actual"]
)。
你也可以使用位置参数和命名参数调用 format()
方法:
s1 = f.format("My {0} {1} {foo:<10} is {bar:<15}!", "real", foo="name")
s2 = f.format(s1, None, "actual", bar="Geraldine")