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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17215400/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 00:47:08  来源:igfitidea点击:

python format string unused named arguments

pythonstringstring-formattingmissing-datadefaultdict

提问by nelsonvarela

Let's say I have:

假设我有:

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

this wil output:

这将输出:

'bond, james bond' 

Next we have:

接下来我们有:

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

this will output:

这将输出:

KeyError: 'james'

Is there some workaround to prevent this error to happen, something like:

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

  • if keyrror: ignore, leave it alone (but do parse others)
  • compare format string with available named arguments, if missing then add
  • 如果 keyrror: 忽略,别管它(但要解析其他人)
  • 将格式字符串与可用的命名参数进行比较,如果缺少则添加

采纳答案by falsetru

If you are using Python 3.2+, use can use str.format_map().

如果您使用的是 Python 3.2+,则可以使用str.format_map()

For bond, bond:

对于bond, bond

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

For bond, {james} bond:

对于bond, {james} bond

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

In Python 2.6/2.7

在 Python 2.6/2.7 中

For bond, bond:

对于bond, bond

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

For bond, {james} 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'

回答by Martin Maillard

You could use a template stringwith the safe_substitutemethod.

您可以在该方法中使用模板字符串safe_substitute

from string import Template

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

回答by dawg

You can follow the recommendation in PEP 3101and subclass Formatter:

您可以遵循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)

Now try it:

现在试试:

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

You can change how key errors are flagged by changing the text in self.defaultto what you would like to show for KeyErrors:

您可以通过将文本更改为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'

The code works unchanged on Python 2.6, 2.7, and 3.0+

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

回答by goodmami

falsetru's answerhas a clever use of a defaulting dictionary with vformat(), and dawg's answeris perhaps more in-line with Python's documentation, but neither handle compound field names (e.g., with explicit conversion (!r) or format specs (:+10g).

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

For example, using falsetru's SafeDict:

例如,使用 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}' {"

And using dawg's MyFormatter:

并使用 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}' {"

Neither work well in the second case because the value lookup (in get_value()) has already stripped out the formatting specifications. Instead, you can redefine vformat()or parse()so these specifications are available. My solution below does this by redefining vformat()so it performs the key lookup and, if the key is missing, escapes the format string with double braces (e.g. {{two!r}}) and then performs the normal vformat().

在第二种情况下都不能很好地工作,因为值查找 (in 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)

Here's it in action:

这是它的实际操作:

>>> 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"

This solution is a bit too hacky (maybe redefining parse()would have fewer kludges), but should work for more formatting strings.

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

回答by Ioannis Filippidis

One can also do the simple and readable, albeit somewhat silly:

也可以做简单易读的事情,虽然有点傻:

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

I know that this answer requires knowledge of the expected keys, but I was looking for a simple two-step substitution (say problem name first, then problem index within a loop) and creating a whole class or unreadable code was more complex than needed.

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

回答by topkara

Needing to partially fill format strings is a common problem when progressively filling the format strings, e.g. for SQL queries.

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

format_partial()method uses the Formatterfrom stringand astto parse the format string and also find out whether the named parameter hash has all the values needed to partially evaluate the format:

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

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_partialwill leave the unresolved portion of the format string, so subsequent calls can be used to resolve those parts as the data is available.

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

goodmami's and dawg's answers seem cleaner, but they both fail to capture the format mini-language completely as in {x:>{x}}; format_partialwill have no problem resolving any format string that string.format()resolves:

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'

It is even easier to extend the functionality to old style format strings using regex instead of the string formatter, as the old style format substrings were regular (ie. no nested markers).

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

回答by feqwix

Here's another way to do it using python27:

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

回答by mattmc3

For Python 3, taking the approved answer, this is a nice, tight, Pythonic implementation:

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

回答by Marcel Wilson

Based on some of the other answers, I expanded the solutions. This will handle strings with formatting spec "{a:<10}".

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

I found that some strings from selenium logging were causing vformat (and format_map) to hit a recursion limit. I also wanted to ensure I could handle strings where empty curly braces exist as well.

我发现来自 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))

output:

输出:

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