Python 部分字符串格式

Python 部分字符串格式,python,string-formatting,Python,String Formatting,是否可以使用高级字符串格式设置方法进行部分字符串格式设置,类似于字符串模板safe\u substitute()函数 例如: s = '{foo} {bar}' s.format(foo='FOO') #Problem: raises KeyError 'bar' >>> s = '{foo} %(bar)s' >>> s = s.format(foo='my_foo') >>> s 'my_foo %(bar)s' >>>

是否可以使用高级字符串格式设置方法进行部分字符串格式设置,类似于字符串模板
safe\u substitute()
函数

例如:

s = '{foo} {bar}'
s.format(foo='FOO') #Problem: raises KeyError 'bar'
>>> s = '{foo} %(bar)s'
>>> s = s.format(foo='my_foo')
>>> s
'my_foo %(bar)s'
>>> s % {'bar': 'my_bar'}
'my_foo my_bar'

如果您定义自己的
格式化程序
,它覆盖
get\u value
方法,则可以使用该方法将未定义的字段名映射到所需的任何内容:

例如,如果
bar
不在kwargs中,您可以将
bar
映射到
“{bar}”


但是,这需要使用格式化程序对象的
format()
方法,而不是字符串的
format()
方法。

您可以通过覆盖映射将其欺骗为部分格式化:

import string

class FormatDict(dict):
    def __missing__(self, key):
        return "{" + key + "}"

s = '{foo} {bar}'
formatter = string.Formatter()
mapping = FormatDict(foo='FOO')
print(formatter.vformat(s, (), mapping))
印刷品

FOO {bar}

当然,此基本实现仅适用于基本情况。

您可以将其封装在采用默认参数的函数中:

def print_foo_bar(foo='', bar=''):
    s = '{foo} {bar}'
    return s.format(foo=foo, bar=bar)

print_foo_bar(bar='BAR') # ' BAR'
多亏了他的评论,我得出了以下结论:

import string

try:
    # Python 3
    from _string import formatter_field_name_split
except ImportError:
    formatter_field_name_split = str._formatter_field_name_split


class PartialFormatter(string.Formatter):
    def get_field(self, field_name, args, kwargs):
        try:
            val = super(PartialFormatter, self).get_field(field_name, args, kwargs)
        except (IndexError, KeyError, AttributeError):
            first, _ = formatter_field_name_split(field_name)
            val = '{' + field_name + '}', first
        return val

如果您知道格式化内容的顺序:

s = '{foo} {{bar}}'
像这样使用它:

ss = s.format(foo='FOO') 
print ss 
>>> 'FOO {bar}'

print ss.format(bar='BAR')
>>> 'FOO BAR'

你不能同时指定
foo
bar
,你必须按顺序指定。

这个
.format()
的限制-无法进行部分替换-一直困扰着我

在评估了如何编写一个定制的
格式化程序
类后,我发现了一个更简单的内置解决方案:

它提供了类似的功能,但也提供了部分替换
safe\u substitute()
方法。模板字符串需要有一个
$
前缀(这感觉有点奇怪-但我认为总体解决方案更好)

基于以下内容形成便利包装:

class StringTemplate(object):
    def __init__(self, template):
        self.template = string.Template(template)
        self.partial_substituted_str = None

    def __repr__(self):
        return self.template.safe_substitute()

    def format(self, *args, **kws):
        self.partial_substituted_str = self.template.safe_substitute(*args, **kws)
        self.template = string.Template(self.partial_substituted_str)
        return self.__repr__()


>>> s = StringTemplate('${x}${y}')
>>> s
'${x}${y}'
>>> s.format(x=1)
'1${y}'
>>> s.format({'y':2})
'12'
>>> print s
12
类似地,基于Sven答案的包装器使用默认字符串格式:

class StringTemplate(object):
    class FormatDict(dict):
        def __missing__(self, key):
            return "{" + key + "}"

    def __init__(self, template):
        self.substituted_str = template
        self.formatter = string.Formatter()

    def __repr__(self):
        return self.substituted_str

    def format(self, *args, **kwargs):
        mapping = StringTemplate.FormatDict(*args, **kwargs)
        self.substituted_str = self.formatter.vformat(self.substituted_str, (), mapping)
# partial string substitution by keyword
>>> f('{foo} {bar}', foo="FOO")
'FOO {bar}'

# partial string substitution by argument
>>> f('{} {bar}', 1)
'1 {bar}'

>>> f('{foo} {}', 1)
'{foo} 1'

# partial string substitution with arguments and keyword mixed
>>> f('{foo} {} {bar} {}', '|', bar='BAR')
'{foo} | BAR {}'

# partial string substitution with extra keyword
>>> f('{foo} {bar}', foo="FOO", bro="BRO")
'FOO {bar}'

# you can simply 'pour out' your dictionary to format function
>>> kwargs = {'foo': 'FOO', 'bro': 'BRO'}
>>> f('{foo} {bar}', **kwargs)
'FOO {bar}'

不确定这是否可以作为快速解决方法,但是

s = '{foo} {bar}'
s.format(foo='FOO', bar='{bar}')
?:)


试试这个。

对我来说,这已经足够好了:

>>> ss = 'dfassf {} dfasfae efaef {} fds'
>>> nn = ss.format('f1', '{}')
>>> nn
'dfassf f1 dfasfae efaef {} fds'
>>> n2 = nn.format('whoa')
>>> n2
'dfassf f1 dfasfae efaef whoa fds'

还有一种方法可以实现这一点,即使用
格式
%
替换变量。例如:

s = '{foo} {bar}'
s.format(foo='FOO') #Problem: raises KeyError 'bar'
>>> s = '{foo} %(bar)s'
>>> s = s.format(foo='my_foo')
>>> s
'my_foo %(bar)s'
>>> s % {'bar': 'my_bar'}
'my_foo my_bar'

您可以使用
functools
中的
partial
函数,该函数很短,可读性最好,并且还描述了编码者的意图:

from functools import partial

s = partial("{foo} {bar}".format, foo="FOO")
print s(bar="BAR")
# FOO BAR

假设在字符串完全填充之前不使用该字符串,则可以执行以下操作:

class IncrementalFormatting:
    def __init__(self, string):
        self._args = []
        self._kwargs = {}
        self._string = string

    def add(self, *args, **kwargs):
        self._args.extend(args)
        self._kwargs.update(kwargs)

    def get(self):
        return self._string.format(*self._args, **self._kwargs)
例如:

template = '#{a}:{}/{}?{c}'
message = IncrementalFormatting(template)
message.add('abc')
message.add('xyz', a=24)
message.add(c='lmno')
assert message.get() == '#24:abc/xyz?lmno'

对我来说,一个非常丑陋但最简单的解决方案就是:

tmpl = '{foo}, {bar}'
tmpl.replace('{bar}', 'BAR')
Out[3]: '{foo}, BAR'

这样,您仍然可以使用
tmpl
作为常规模板,并仅在需要时执行部分格式化。我发现这个问题太小,无法使用像Mohan Raj这样的过度杀戮解决方案。

我的建议如下(使用Python3.6测试):

更新:
这里展示了一种更为优雅的方式(子类化
dict
和重载
\uuuuuu missing\uuuuu(self,key)
):

在测试了和的最有希望的解决方案后,我意识到它们都没有真正满足以下要求:

  • 严格遵守模板的
    str.format\u map()
    识别的语法
  • 能够保留复杂的格式,即完全支持
  • 因此,我编写了自己的解决方案,满足上述要求。 (编辑:现在@SvenMarnach的版本——正如这个答案中所报告的——似乎可以处理我所需要的角落案例)

    基本上,我最终解析了模板字符串,找到了匹配的嵌套
    {.*.}
    组(使用
    find_all()
    helper函数),并使用
    str.format_map()
    直接逐步构建格式化字符串,同时捕获任何潜在的
    keyrerror

    def find_all(
            text,
            pattern,
            overlap=False):
        """
        Find all occurrencies of the pattern in the text.
    
        Args:
            text (str|bytes|bytearray): The input text.
            pattern (str|bytes|bytearray): The pattern to find.
            overlap (bool): Detect overlapping patterns.
    
        Yields:
            position (int): The position of the next finding.
        """
        len_text = len(text)
        offset = 1 if overlap else (len(pattern) or 1)
        i = 0
        while i < len_text:
            i = text.find(pattern, i)
            if i >= 0:
                yield i
                i += offset
            else:
                break
    
    (这段代码也可以在--DISCLAIMER:我是它的主要作者。)


    此代码的用法是:

    print(safe_format_map('{a} {b} {c}', dict(a=-A-)))
    # -A- {b} {c}
    

    让我们将此与我最喜欢的解决方案(由@SvenMarnach编写,他友好地分享了他的代码和代码):

    以下是几个测试:

    test_texts = (
        '{b} {f}',  # simple nothing useful in source
        '{a} {b}',  # simple
        '{a} {b} {c:5d}',  # formatting
        '{a} {b} {c!s}',  # coercion
        '{a} {b} {c!s:>{a}s}',  # formatting and coercion
        '{a} {b} {c:0{a}d}',  # nesting
        '{a} {b} {d[x]}',  # dicts (existing in source)
        '{a} {b} {e.index}',  # class (existing in source)
        '{a} {b} {f[g]}',  # dict (not existing in source)
        '{a} {b} {f.values}',  # class (not existing in source)
    
    )
    source = dict(a=4, c=101, d=dict(x='FOO'), e=[])
    
    以及使其运行的代码:

    funcs = safe_format_map, safe_format_alt
    
    n = 18
    for text in test_texts:
        full_source = {**dict(b='---', f=dict(g='Oh yes!')), **source}
        print('{:>{n}s} :   OK   : '.format('str.format_map', n=n) + text.format_map(full_source))
        for func in funcs:
            try:
                print(f'{func.__name__:>{n}s} :   OK   : ' + func(text, source))
            except:
                print(f'{func.__name__:>{n}s} : FAILED : {text}')
    
    导致:

    str.format\u map:OK:--{'g':'Oh yes!'
    安全格式映射:OK:{b}{f}
    安全格式\u alt:OK:{b}{f}
    str.format_映射:确定:4---
    安全格式映射:OK:4{b}
    安全格式\u alt:OK:4{b}
    str.format\u映射:确定:4---101
    安全格式映射:OK:4{b}101
    安全格式alt:OK:4{b}101
    str.format\u映射:确定:4---101
    安全格式映射:OK:4{b}101
    安全格式alt:OK:4{b}101
    str.format\u映射:确定:4---101
    安全格式映射:OK:4{b}101
    安全格式alt:OK:4{b}101
    str.format_映射:OK:4---0101
    安全格式映射:OK:4{b}0101
    安全格式可选:OK:4{b}0101
    str.format\u映射:OK:4---FOO
    安全格式映射:OK:4{b}FOO
    安全格式可选:OK:4{b}FOO
    str.format_映射:确定:4--
    安全格式映射:OK:4{b}
    安全格式\u alt:OK:4{b}
    街道图:好的:4--哦,是的!
    安全格式映射:OK:4{b}{f[g]}
    安全格式(alt:OK:4{b}{f[g]}
    str.format_映射:确定:4--
    安全格式映射:OK:4{b}{f.values}
    安全格式\u alt:OK:4{b}{f.values}
    
    正如您所看到的,更新的版本现在似乎能够很好地处理早期版本失败的情况


    就时间而言,它们之间的距离大约在50%以内,这取决于要格式化的实际
    文本
    (很可能是实际的
    ),但是
    safe\u format\u map()
    在我执行的大多数测试中似乎都有优势(当然,不管它们是什么意思):

    {b}{f}
    每个回路3.93 ms±153µs(7次运行的平均值±标准偏差,每个100个回路)
    每个回路6.35 ms±51.9µs(7次运行的平均值±标准偏差,每个100个回路)
    {a} {b}
    每个回路4.37 ms±57.1µs(7次运行的平均值±标准偏差,每个100个回路)
    每个回路5.2 ms±159µs
    
    import string
    
    
    class FormatPlaceholder:
        def __init__(self, key):
            self.key = key
        def __format__(self, spec):
            result = self.key
            if spec:
                result += ":" + spec
            return "{" + result + "}"
        def __getitem__(self, index):
            self.key = "{}[{}]".format(self.key, index)
            return self
        def __getattr__(self, attr):
            self.key = "{}.{}".format(self.key, attr)
            return self
    
    
    class FormatDict(dict):
        def __missing__(self, key):
            return FormatPlaceholder(key)
    
    
    def safe_format_alt(text, source):
        formatter = string.Formatter()
        return formatter.vformat(text, (), FormatDict(source))
    
    test_texts = (
        '{b} {f}',  # simple nothing useful in source
        '{a} {b}',  # simple
        '{a} {b} {c:5d}',  # formatting
        '{a} {b} {c!s}',  # coercion
        '{a} {b} {c!s:>{a}s}',  # formatting and coercion
        '{a} {b} {c:0{a}d}',  # nesting
        '{a} {b} {d[x]}',  # dicts (existing in source)
        '{a} {b} {e.index}',  # class (existing in source)
        '{a} {b} {f[g]}',  # dict (not existing in source)
        '{a} {b} {f.values}',  # class (not existing in source)
    
    )
    source = dict(a=4, c=101, d=dict(x='FOO'), e=[])
    
    funcs = safe_format_map, safe_format_alt
    
    n = 18
    for text in test_texts:
        full_source = {**dict(b='---', f=dict(g='Oh yes!')), **source}
        print('{:>{n}s} :   OK   : '.format('str.format_map', n=n) + text.format_map(full_source))
        for func in funcs:
            try:
                print(f'{func.__name__:>{n}s} :   OK   : ' + func(text, source))
            except:
                print(f'{func.__name__:>{n}s} : FAILED : {text}')
    
    for text in test_texts:
        print(f'  {text}')
        %timeit safe_format(text * 1000, source)
        %timeit safe_format_alt(text * 1000, source)
    
    s = '{foo} {bar}'
    
    replacements = {'foo': 'FOO'}
    
    s.format(**replacements)
    #---------------------------------------------------------------------------
    #KeyError                                  Traceback (most recent call last)
    #<ipython-input-29-ef5e51de79bf> in <module>()
    #----> 1 s.format(**replacements)
    #
    #KeyError: 'bar'
    
    from string import Formatter
    args = {x[1]:'{'+x[1]+'}' for x in Formatter().parse(s)}
    print(args)
    #{'foo': '{foo}', 'bar': '{bar}'}
    
    new_s = s.format(**{**args, **replacements}}
    print(new_s)
    #'FOO {bar}'
    
    args.update(replacements)
    print(s.format(**args))
    #'FOO {bar}'
    
    # partial string substitution by keyword
    >>> f('{foo} {bar}', foo="FOO")
    'FOO {bar}'
    
    # partial string substitution by argument
    >>> f('{} {bar}', 1)
    '1 {bar}'
    
    >>> f('{foo} {}', 1)
    '{foo} 1'
    
    # partial string substitution with arguments and keyword mixed
    >>> f('{foo} {} {bar} {}', '|', bar='BAR')
    '{foo} | BAR {}'
    
    # partial string substitution with extra keyword
    >>> f('{foo} {bar}', foo="FOO", bro="BRO")
    'FOO {bar}'
    
    # you can simply 'pour out' your dictionary to format function
    >>> kwargs = {'foo': 'FOO', 'bro': 'BRO'}
    >>> f('{foo} {bar}', **kwargs)
    'FOO {bar}'
    
    from string import Formatter
    
    
    class FormatTuple(tuple):
        def __getitem__(self, key):
            if key + 1 > len(self):
                return "{}"
            return tuple.__getitem__(self, key)
    
    
    class FormatDict(dict):
        def __missing__(self, key):
            return "{" + key + "}"
    
    
    def f(string, *args, **kwargs):
        """
        String safe substitute format method.
        If you pass extra keys they will be ignored.
        If you pass incomplete substitute map, missing keys will be left unchanged.
        :param string:
        :param kwargs:
        :return:
    
        >>> f('{foo} {bar}', foo="FOO")
        'FOO {bar}'
        >>> f('{} {bar}', 1)
        '1 {bar}'
        >>> f('{foo} {}', 1)
        '{foo} 1'
        >>> f('{foo} {} {bar} {}', '|', bar='BAR')
        '{foo} | BAR {}'
        >>> f('{foo} {bar}', foo="FOO", bro="BRO")
        'FOO {bar}'
        """
        formatter = Formatter()
        args_mapping = FormatTuple(args)
        mapping = FormatDict(kwargs)
        return formatter.vformat(string, args_mapping, mapping)
    
    SafeFormatter().format("{a:<5} {b:<10}", a=10)
    
    Testing: {a} 
       safe_format_map : OK         : {a} 
       safe_format_map : OK         : 10 
            format_map : FAILED
            format_map : OK         : 10 
    Testing: {a:5d}
       safe_format_map : OK         : {a:5d}
       safe_format_map : OK         :    10
            format_map : FAILED
            format_map : OK         :    10
    Testing: {a!s}
       safe_format_map : OK         : {a!s}
       safe_format_map : OK         : 10
            format_map : FAILED
            format_map : OK         : 10
    Testing: {a!s:>{a}s}
       safe_format_map : OK         : {a!s:>{a}s}
       safe_format_map : OK         :         10
            format_map : FAILED
            format_map : OK         :         10
    Testing: {a:0{a}d}
       safe_format_map : OK         : {a:0{a}d}
       safe_format_map : OK         : 0000000010
            format_map : FAILED
            format_map : OK         : 0000000010
    Testing: {d[x]}
       safe_format_map : OK         : {d[x]}
       safe_format_map : OK         : FOO
            format_map : FAILED
            format_map : OK         : FOO
    Testing: {d.values}
       safe_format_map : OK         : {d.values}
       safe_format_map : OK         : <built-in method values of dict object at 0x7fe61e230af8>
            format_map : FAILED
            format_map : OK         : <built-in method values of dict object at 0x7fe61e230af8>
    
    def partial_format(s, **kwargs):
        parts = re.split(r'(\{[^}]*\})', s)
        for k, v in kwargs.items():
            for idx, part in enumerate(parts):
                if re.match(rf'\{{{k}[!:}}]', part):  # Placeholder keys must always be followed by '!', ':', or the closing '}'
                    parts[idx] = parts[idx].format_map({k: v})
        return ''.join(parts)
    
    # >>> partial_format('{foo} {bar:1.3f}', foo='FOO')
    # 'FOO {bar:1.3f}'
    # >>> partial_format('{foo} {bar:1.3f}', bar=1)
    # '{foo} 1.000'
    
    text = "{bar}, {foo}, {foobar[a]}"
    text.format_map(DefaultWrapper(bar="A")) # "A, , " (missing replaced with empty str)
    
    # Even this works:
    foobar = {"c": "C"}
    text = "{foobar[a]}, {foobar[c]}"
    text.format_map(DefaultWrapper(foobar=foobar)) # ", C" missing indices are also replaced