格式化字符串未使用的命名参数[重复]

Posted

技术标签:

【中文标题】格式化字符串未使用的命名参数[重复]【英文标题】:Format string unused named arguments [duplicate] 【发布时间】:2013-06-17 09:55:48 【问题描述】:

假设我有:

action = 'bond, james bond'.format(bond='bond', james='james')

这将输出:

'bond, james bond' 

接下来我们有:

 action = 'bond, james bond'.format(bond='bond')

这将输出:

KeyError: 'james'

是否有一些解决方法可以防止发生此错误,例如:

如果 keyrror: 忽略,不管它(但要解析其他) 将格式字符串与可用的命名参数进行比较,如果缺少则添加

【问题讨论】:

你想要哪个bond, bond/bond, james, bond 我认为第二个更好。第一个可以创建奇怪的内容..第二个让人们认为“嘿,这里出了点问题”,在这种情况下这是一件好事 我更新了这两种情况的答案。 ***.com/questions/5466451/… 另见: ***.com/questions/35574349/… 【参考方案1】:

如果你使用 Python 3.2+,可以使用str.format_map()。

对于bond, bond

>>> from collections import defaultdict
>>> 'bond, james bond'.format_map(defaultdict(str, bond='bond'))
'bond,  bond'

对于bond, james bond

>>> class SafeDict(dict):
...     def __missing__(self, key):
...         return '' + key + ''
...
>>> 'bond, james bond'.format_map(SafeDict(bond='bond'))
'bond, james bond'

在 Python 2.6/2.7 中

对于bond, bond

>>> from collections import defaultdict
>>> import string
>>> string.Formatter().vformat('bond, james bond', (), defaultdict(str, bond='bond'))
'bond,  bond'

对于bond, james bond

>>> from collections import defaultdict
>>> import string
>>>
>>> class SafeDict(dict):
...     def __missing__(self, key):
...         return '' + key + ''
...
>>> string.Formatter().vformat('bond, james bond', (), SafeDict(bond='bond'))
'bond, james bond'

【讨论】:

我正在使用 2.7.. 谢谢!我要测试一下 奇怪的是,在 python3 中,format 给出的结果与此示例中的 format_map 相同。 @frnhr,你是什么意思? 'bond, james bond'.format(defaultdict(str, bond='bond')) 给我KeyError @frnhr,你能举出你试过的具体例子吗? 这里是:'bond, james bond'.format(**SafeDict(bond='bond')) 返回'bond, james bond'。注意**。实际上直到现在我才注意到它:) Python 3.4.3【参考方案2】:

您可以将template string 与safe_substitute 方法一起使用。

from string import Template

tpl = Template('$bond, $james $bond')
action = tpl.safe_substitute('bond': 'bond')

【讨论】:

几年前我就已经知道这个技巧了,但是整个问答仍然再次确认Template 在这种情况下是最好的。赞成。 :-) 迄今为止最简单的方法,不会让您陷入认为它可以处理更复杂的格式化程序(如my_float:>5.2f)的陷阱。这显然是普通的旧字符串替换。【参考方案3】:

您可以按照PEP 3101 和子类 Formatter 中的建议:

from __future__ import print_function
import string

class MyFormatter(string.Formatter):
    def __init__(self, default='0'):
        self.default=default

    def get_value(self, key, args, kwds):
        if isinstance(key, str):
            return kwds.get(key, self.default.format(key))
        else:
            return string.Formatter.get_value(key, args, kwds)

现在试试吧:

>>> fmt=MyFormatter()
>>> fmt.format("bond, james bond", bond='bond', james='james')
'bond, james bond'
>>> fmt.format("bond, james bond", bond='bond')
'bond, james bond'

您可以通过将self.default 中的文本更改为您希望为 KeyErrors 显示的内容来更改关键错误的标记方式:

>>> fmt=MyFormatter('">>0 KeyError<<"')
>>> fmt.format("bond, james bond", bond='bond', james='james')
'bond, james bond'
>>> fmt.format("bond, james bond", bond='bond')
'bond, ">>james KeyError<<" bond'

代码在 Python 2.6、2.7 和 3.0+ 上保持不变

【讨论】:

在此处提供的答案中,我认为这在便携性/优雅 +1 方面是最好的 我认为 Formatter.get_value(key, args, kwds) 应该 return string.Formatter.get_value(self, key, args, kwds) 在你的代码中 @GrijeshChauhan 我不确定......该函数被递归调用,唯一真正的返回发生在最后一个终端,调用所以......无论如何我没有设法让它工作。我完成了一个更简单的代码,它可以满足我的需求。 class URLFormatter(string.Formatter): def __init__(self, default=''): self.default=default def get_value(self, key, args, kwds): return kwds.get(key, self.default.format (键))【参考方案4】:

也可以做到简单易读,虽然有点傻:

'bar, fro bar'.format(bar='bar', fro='fro')

我知道这个答案需要了解预期的键, 但我一直在寻找一个简单的两步替换(先说问题名称,然后是循环中的问题索引)并且创建一个完整的类或不可读的代码比需要的更复杂。

【讨论】:

【参考方案5】:

falsetru's answer 巧妙地使用了带有vformat() 的默认字典,dawg's answer 可能更符合 Python 的文档,但它们都不能处理复合字段名称(例如,使用显式转换 (!r)或格式规范 (:+10g)。

例如,使用 falsetru 的 SafeDict:

>>> string.Formatter().vformat('one one:x one:10f two!r two[0]', (), SafeDict(one=215, two=['James', 'Bond']))
"215 d7 215.000000 ['James', 'Bond'] James"
>>> string.Formatter().vformat('one one:x one:10f two!r two[0]', (), SafeDict(one=215))
"215 d7 215.000000 'two' "

并使用 dawg 的 MyFormatter:

>>> MyFormatter().format('one one:x one:10f two!r two[0]', one=215, two=['James', 'Bond'])
"215 d7 215.000000 ['James', 'Bond'] James"
>>> MyFormatter().format('one one:x one:10f two!r two[0]', one=215)
"215 d7 215.000000 'two' "

在第二种情况下都不能正常工作,因为值查找(在get_value() 中)已经剥离了格式规范。相反,您可以重新定义vformat()parse(),以便这些规范可用。我下面的解决方案通过重新定义vformat() 来执行此操作,因此它执行密钥查找,如果缺少密钥,则使用双括号(例如two!r)转义格式字符串,然后执行正常的vformat()

class SafeFormatter(string.Formatter):
    def vformat(self, format_string, args, kwargs):
        args_len = len(args)  # for checking IndexError
        tokens = []
        for (lit, name, spec, conv) in self.parse(format_string):
            # re-escape braces that parse() unescaped
            lit = lit.replace('', '').replace('', '')
            # only lit is non-None at the end of the string
            if name is None:
                tokens.append(lit)
            else:
                # but conv and spec are None if unused
                conv = '!' + conv if conv else ''
                spec = ':' + spec if spec else ''
                # name includes indexing ([blah]) and attributes (.blah)
                # so get just the first part
                fp = name.split('[')[0].split('.')[0]
                # treat as normal if fp is empty (an implicit
                # positional arg), a digit (an explicit positional
                # arg) or if it is in kwargs
                if not fp or fp.isdigit() or fp in kwargs:
                    tokens.extend([lit, '', name, conv, spec, ''])
                # otherwise escape the braces
                else:
                    tokens.extend([lit, '', name, conv, spec, ''])
        format_string = ''.join(tokens)  # put the string back together
        # finally call the default formatter
        return string.Formatter.vformat(self, format_string, args, kwargs)

下面是实际操作:

>>> SafeFormatter().format('one one:x one:10f two!r two[0]', one=215, two=['James', 'Bond'])
"215 d7 215.000000 ['James', 'Bond'] James"
>>> SafeFormatter().format('one one:x one:10f two!r two[0]', one=215)
'215 d7 215.000000 two!r two[0]'
>>> SafeFormatter().format('one one:x one:10f two!r two[0]')
'one one:x one:10f two!r two[0]'
>>> SafeFormatter().format('one one:x one:10f two!r two[0]', two=['James', 'Bond'])
"one one:x one:10f ['James', 'Bond'] James"

这个解决方案有点太老套了(也许重新定义parse() 会有更少的麻烦),但应该适用于更多的格式化字符串。

【讨论】:

非常好的解决方案【参考方案6】:

在逐步填充格式字符串时,需要部分填充格式字符串是一个常见问题,例如用于 SQL 查询。

format_partial() 方法使用来自stringastFormatter 来解析格式字符串,并找出命名参数哈希是否具有部分评估格式所需的所有值:

import ast
from collections import defaultdict
from itertools import chain, ifilter, imap
from operator import itemgetter
import re
from string import Formatter

def format_partial(fstr, **kwargs):
    def can_resolve(expr, **kwargs):
        walk = chain.from_iterable(imap(ast.iter_fields, ast.walk(ast.parse(expr))))
        return all(v in kwargs for k,v in ifilter(lambda (k,v): k=='id', walk))

    ostr = fstr
    fmtr = Formatter()
    dd = defaultdict(int)
    fmtr.get_field = lambda field_name, args, kwargs: (dd[field_name],field_name)
    fmtr.check_unused_args = lambda used_args, args, kwargs: all(v in dd for v in used_args)
    for t in ifilter(itemgetter(1), Formatter().parse(fstr)):
        f = ''+t[1]+(':'+t[2] if t[2] else '')+''
        dd = defaultdict(int)
        fmtr.format(f,**kwargs)
        if all(can_resolve(e,**kwargs) for e in dd):
            ostr = re.sub(re.escape(f),Formatter().format(f, **kwargs),ostr,count=1)
    return ostr

format_partial 将保留格式字符串的未解析部分,因此当数据可用时,后续调用可用于解析这些部分。

goodmami 和 dawg 的答案似乎更清晰,但他们都未能像 x:&gt;x 那样完全捕获格式迷你语言; format_partial 解析string.format() 解析的任何格式字符串都没有问题:

from datetime import date
format_partial('x  y[1]:x x:>x z.year', **'x':30, 'y':[1,2], 'z':date.today())

'30  2                             30 2016'

使用正则表达式而不是字符串格式化程序更容易将功能扩展到旧式格式字符串,因为旧式格式子字符串是常规的(即没有嵌套标记)。

【讨论】:

代码通过格式模板替换已解析模板的方式现在是一个大问题。 或许您也应该说明问题所在?【参考方案7】:

这是使用 python27 的另一种方法:

action = 'bond, james bond'
d = dict((x[1], '') for x in action._formatter_parser())
# Now we have: `d = 'james': '', 'bond': ''`.
d.update(bond='bond')
print action.format(**d)  # bond,  bond

【讨论】:

有些人可能会拒绝使用 _formatter_parser,但对我来说这是最 Pythonic 的方法:简单、易于理解、使用开箱即用的功能,如果您修改在d = dict((x[1], ''+str(x[1])+'') for x in action._formatter_parser()) 的第二行,您可以获得bond, james bond 格式,就像bond, bond 格式一样容易。【参考方案8】:

对于 Python 3,采用批准的答案,这是一个不错的、紧凑的 Pythonic 实现:

def safeformat(str, **kwargs):
    class SafeDict(dict):
        def __missing__(self, key):
            return '' + key + ''
    replacements = SafeDict(**kwargs)
    return str.format_map(replacements)

# In [1]: safeformat("a: a, b: b, c: c", a="A", c="C", d="D")
# Out[1]: 'a: A, b: b, c: C'

【讨论】:

不幸的是,这将无法处理 "a:&lt;10" 的字符串大小写。【参考方案9】:

根据其他一些答案,我扩展了解决方案。 这将处理格式规范 "a:&lt;10" 的字符串。

我发现 selenium 日志记录中的一些字符串导致 vformat(和 format_map)达到递归限制。我还想确保我可以处理也存在空花括号的字符串。

def partialformat(s: str, recursionlimit: int = 10, **kwargs):
    """
    vformat does the acutal work of formatting strings. _vformat is the 
    internal call to vformat and has the ability to alter the recursion 
    limit of how many embedded curly braces to handle. But for some reason 
    vformat does not.  vformat also sets the limit to 2!   

    The 2nd argument of _vformat 'args' allows us to pass in a string which 
    contains an empty curly brace set and ignore them.
    """

    class FormatPlaceholder:
        def __init__(self, key):
            self.key = key

        def __format__(self, spec):
            result = self.key
            if spec:
                result += ":" + spec
            return "" + result + ""

    class FormatDict(dict):
        def __missing__(self, key):
            return FormatPlaceholder(key)

    class PartialFormatter(string.Formatter):
        def get_field(self, field_name, args, kwargs):
            try:
                obj, first = super(PartialFormatter, self).get_field(field_name, args, kwargs)
            except (IndexError, KeyError, AttributeError):
                first, rest = formatter_field_name_split(field_name)
                obj = '' + field_name + ''

                # loop through the rest of the field_name, doing
                #  getattr or getitem as needed
                for is_attr, i in rest:
                    if is_attr:
                        try:
                            obj = getattr(obj, i)
                        except AttributeError as exc:
                            pass
                    else:
                        obj = obj[i]

            return obj, first

    fmttr = string.Formatter()
    fs, _ = fmttr._vformat(s, ("",), FormatDict(**kwargs), set(), recursionlimit)
    return fs

class ColorObj(object):
    blue = "^BLUE^"
s = '"a": "b": "c": "d" :  foo:<12 & foo!r arg color.blue:<10 color.pink blah.atr '
print(partialformat(s, foo="Fooolery", arg="ARRrrrrrg!", color=ColorObj))

输出:

"a": "b": "c": "d" :  Fooolery             & 'Fooolery' Fooolery ARRrrrrrg! ^BLUE^ color.pink blah.atr 

【讨论】:

以上是关于格式化字符串未使用的命名参数[重复]的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中格式化字符串和命名参数

02python程序设计基础——字符串

在 JDBC 中使用命名参数时是不是有任何非法字符?

带命名参数的字符串格式

JavaScript:未命名函数的参数

Flutter TextSelection.collapsed:未定义命名参数“offset”