Python字符串格式化抑制/静默KeyError/IndexError

20 投票
4 回答
18138 浏览
提问于 2025-04-16 03:06

有没有办法让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 个回答

5

很遗憾,默认情况下是没有这种方法的。不过,你可以使用一个叫做 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
20

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

23

官方的解决方案是通过创建一个字典的子类,并定义一个特殊的方法 __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")

撰写回答