python格式字符串未使用的命名参数

时间:2013-06-20 13:50:19

标签: python string string-formatting missing-data defaultdict

让我说我有:

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

这个输出:

'bond, james bond' 

接下来我们有:

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

这将输出:

KeyError: 'james'

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

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

9 个答案:

答案 0 :(得分:69)

如果您使用的是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'

答案 1 :(得分:19)

您可以使用safe_substitute方法使用template string

from string import Template

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

答案 2 :(得分:10)

您可以按照PEP 3101中的建议和子类格式化程序:

Dim lines As New List(Of String)

For Each row As DataGridViewRow In myDataGridView.SelectedRows
    lines.Add(row.Cells(1).Value.ToString())
Next

myTextBox.Lines = lines.ToArray()

现在尝试一下:

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' 中的文字更改为您要为KeyErrors显示的内容来更改标记错误的方式:

self.default

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

答案 3 :(得分:8)

人们也可以做到简单易读,虽然有些愚蠢:

'{bond}, {james} {bond}'.format(bond='bond', james='{james}')

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

答案 4 :(得分:6)

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}})转义格式字符串,然后执行正常{{1} }。

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)

这个解决方案有点太hacky(可能重新定义>>> 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" 会有更少的kludges),但应该适用于更多格式化字符串。

答案 5 :(得分:2)

这是使用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

答案 6 :(得分:1)

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

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

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:>{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 :(得分:0)

对于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'

答案 8 :(得分:0)

基于其他一些答案,我扩展了解决方案。 这将处理格式为"{a:<10}"的字符串。

我发现硒记录中的一些字符串导致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} }}}}