格式映射中字符串的官方解决方案(Python 3 Docs)是继承dict类并定义魔术方法__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 的行为有所不同。在 v3.5.3 中,调用是成功的 __getitem__(self, "foo") 和找不到密钥 "bar" 的 __getitem__(self, "bar"),因此它调用 __missing__(self, "bar") 来处理丢失的密钥而不抛出 KeyError。在 v3.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__()。
以下似乎是最兼容的解决方案,因为它也适用于较旧的 Python 版本,包括 2.x(我测试了 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}),需要对格式化程序进行子类化:
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")