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