【问题标题】:python string format suppress/silent keyerror/indexerror [duplicate]python字符串格式抑制/静默keyerror/indexerror [重复]
【发布时间】:2011-04-01 23:12:33
【问题描述】:

有没有办法使用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 ""

这似乎提供了所需的行为。

【问题讨论】:

  • 您也可以使用str.format_map(),如this answer中所述。

标签: string format python


【解决方案1】:

不幸的是,不,默认情况下没有这种方法。但是,您可以为它提供 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

【讨论】:

  • 谢谢。它很有用,但正如 Fabian 所提到的,它不处理 dict 以外的映射对象
【解决方案2】:

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)

您使用返回“”的str() 工厂创建默认字典。然后你为 defaultdict 创建一个键。在格式字符串中,您访问传递的第一个对象的键。这样做的好处是允许您传递其他键和值,只要您的 defaultdict 是 format() 的第一个参数。

另外,请参阅http://bugs.python.org/issue6081

【讨论】:

  • 可以通过使用格式化程序对象(来自错误报告链接的想法)而不是 defaultdict 来改进吗?这样,不需要更改格式变量。谢谢
  • 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 "" 这对我有用。优点是不需要创建额外的 defaultdict。
  • 是的,效果很好。我不确定如何比较额外 defaultdict 的惩罚与额外 Formatter 类+对象的惩罚,但在某些情况下它可能会更好,特别是如果它使代码更清晰。
  • 要返回任意字符串,您可以使用 lambda 表达式,因为 defaultdict 需要一个可调用对象:"Hello {name}!".format(**defaultdict(lambda: "World"))(结果:Hello World!
  • 我不确定defaultdict 是否曾经用作关键字参数,但在 Python 3.8 中,@CodeManX 的最新建议不起作用。
【解决方案3】:

我制作了一个与 Daniel 的方法类似但没有 {0.x} 属性访问权限的版本。

import string    
class SafeFormat(object):
    def __init__(self, **kw):
        self.__dict = kw

    def __getitem__(self, name):
        return self.__dict.get(name, '{%s}' % name)



string.Formatter().vformat('{what} {man}', [], SafeFormat(man=2))

打印出来

'{what} 2'

【讨论】:

    【解决方案4】:

    格式映射中字符串的官方解决方案(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")
    

    【讨论】:

    • .format(**format_dict({"foo": "name"})) 当然也可以使用。但是,由于语法错误,您的 sn-p 将失败。 .format()的dict解包**是强制的,不直接接受dicts。
    • 考虑从__missing__返回'{' + key + '}'而不是...,因为如果它不是替换字典中的有效键,您希望保持大括号内的文本不变。一个很好的例子是 HTML 模板替换,其中模板包含
    • 我试过这个但没有用。我认为您的意思是使用 format_map() 而不是 format()
    • @santileortiz 我添加了其他解决方案并解释了为什么我的原始答案在最近的 Python 版本中不再适用。我还添加了一个示例,该示例将占位符保留在字符串中,类似于 MikeEllis 建议的内容。
    • 我认为您可以稍微简化这个非常好的解决方案,但将Unformatted.format 重命名为Unformatted.__format__。然后,您可以完全删除Formatter.format_field,因为Unformatted 被原始string.Formatter.format_field 正确处理。同样,vformat 可以通过,因为它只将参数转发给等效的超类。
    猜你喜欢
    • 2015-01-07
    • 2013-04-27
    • 2017-03-26
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多