使用高级字符串格式化方法,类似于字符串模板中的safe_substitute()
函数,是否可以进行部分字符串格式化?
例如:
s = '{foo} {bar}'
s.format(foo='FOO') #Problem: raises KeyError 'bar'
使用高级字符串格式化方法,类似于字符串模板中的safe_substitute()
函数,是否可以进行部分字符串格式化?
例如:
s = '{foo} {bar}'
s.format(foo='FOO') #Problem: raises KeyError 'bar'
functools
库中的partial
函数,它既简短易懂,也能明确程序员的意图。from functools import partial
s = partial("{foo} {bar}".format, foo="FOO")
print s(bar="BAR")
# FOO BAR
from functools import partial
s = "{foo} {bar}".format
s_foo = partial(s, foo="FOO")
print(s_foo(bar="BAR")) # FOO BAR
print(s(foo="FOO", bar="BAR")) # FOO BAR
- Paul Brownpartial()
方法是无法帮助我的。 - Delgan"{foo} {{bar}}".format(foo="{bar}").format(bar="123")
,与其他示例不同。 我期望得到"{bar} 123"
,但它们却输出了"123 123"
。 - Benjamin Manns如果您知道您正在格式化的顺序:
s = '{foo} {{bar}}'
使用方法如下:
ss = s.format(foo='FOO')
print ss
>>> 'FOO {bar}'
print ss.format(bar='BAR')
>>> 'FOO BAR'
你不能同时指定foo
和bar
- 你必须按顺序执行。
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}
当然,这种基本实现只适用于基本情况。
{bar:1.2f}
。 - MaxNoe__missing__()
中,不要返回字符串,而是返回一个自定义类的实例,并在该类中重写__format__()
方法,以包含原始占位符和格式规范。这是一个概念验证:http://ideone.com/xykV7R - Sven Marnachpartial
,就像其他答案中所示。 - Leo.format()
的局限性 - 无法进行部分替换 - 一直让我感到困扰。
在评估编写自定义 Formatter
类(如许多答案中所描述的)甚至考虑使用第三方软件包(如lazy_format)之后,我发现了一个更简单的内置解决方案:模板字符串
它提供了类似的功能,但还通过 safe_substitute()
方法提供了部分替换。模板字符串需要有一个 $
前缀(这感觉有些奇怪 - 但我认为总体解决方案更好)。
import string
template = string.Template('${x} ${y}')
try:
template.substitute({'x':1}) # raises KeyError
except KeyError:
pass
# but the following raises no error
partial_str = template.safe_substitute({'x':1}) # no error
# partial_str now contains a string with partial substitution
partial_template = string.Template(partial_str)
substituted_str = partial_template.safe_substitute({'y':2}) # no error
print substituted_str # prints '12'
基于这个,形成一个便捷的包装器:
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)
不确定这是否可以作为一个快速解决方法,但怎么样?
s = '{foo} {bar}'
s.format(foo='FOO', bar='{bar}')
?:)
'{foo} {bar:3.6f}'.format(foo='FOO', bar='{bar}')
将会出现错误 ValueError: Unknown format code 'f' for object of type 'str'
。 - 0x5453Formatter
并重写get_value
方法,则可以使用它将未定义的字段名称映射到任何您想要的内容:bar
不在kwargs中,则可以将其映射为"{bar}"
。format()
方法,而不是字符串的format()
方法。
http://docs.python.org/library/string.html#string.Formatter.get_value>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)
'fd:123:{topic_id}'
试一下这个。
{{
和 }}
是一种转义格式标记的方式,因此 format()
不会执行替换操作,而是将 {{
和 }}
分别替换为 {
和 }
。 - 7yl4r{{}}
只适用于一个格式,如果您需要应用更多,您需要添加更多{}
。例如:'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)
将返回错误,因为第二个格式没有提供topic_id
值。 - Franzi感谢 Amber 的评论,我想出了这个:
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
{field!s: >4}
变成{field}
)。 - Brendan Abel我找到的所有解决方案似乎都存在更高级规范或转换选项的问题。@SvenMarnach的FormatPlaceholder非常聪明,但是它无法正确处理强制转换(例如{a!s:>2s}
),因为它调用了__str__
方法(在此示例中),而不是__format__
,导致您失去了任何其他格式。
这是我最终得出的一些关键特点:
sformat('The {} is {}', 'answer')
'The answer is {}'
sformat('The answer to {question!r} is {answer:0.2f}', answer=42)
'The answer to {question!r} is 42.00'
sformat('The {} to {} is {:0.{p}f}', 'answer', 'everything', p=4)
'The answer to everything is {:0.4f}'
str.format
相似的接口(不仅仅是映射){k!s}
{!r}
{k:>{size}}
{k.foo}
{k[0]}
{k!s:>{size}}
import string
class SparseFormatter(string.Formatter):
"""
A modified string formatter that handles a sparse set of format
args/kwargs.
"""
# re-implemented this method for python2/3 compatibility
def vformat(self, format_string, args, kwargs):
used_args = set()
result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
self.check_unused_args(used_args, args, kwargs)
return result
def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
auto_arg_index=0):
if recursion_depth < 0:
raise ValueError('Max string recursion exceeded')
result = []
for literal_text, field_name, format_spec, conversion in \
self.parse(format_string):
orig_field_name = field_name
# output the literal text
if literal_text:
result.append(literal_text)
# if there's a field, output it
if field_name is not None:
# this is some markup, find the object and do
# the formatting
# handle arg indexing when empty field_names are given.
if field_name == '':
if auto_arg_index is False:
raise ValueError('cannot switch from manual field '
'specification to automatic field '
'numbering')
field_name = str(auto_arg_index)
auto_arg_index += 1
elif field_name.isdigit():
if auto_arg_index:
raise ValueError('cannot switch from manual field '
'specification to automatic field '
'numbering')
# disable auto arg incrementing, if it gets
# used later on, then an exception will be raised
auto_arg_index = False
# given the field_name, find the object it references
# and the argument it came from
try:
obj, arg_used = self.get_field(field_name, args, kwargs)
except (IndexError, KeyError):
# catch issues with both arg indexing and kwarg key errors
obj = orig_field_name
if conversion:
obj += '!{}'.format(conversion)
if format_spec:
format_spec, auto_arg_index = self._vformat(
format_spec, args, kwargs, used_args,
recursion_depth, auto_arg_index=auto_arg_index)
obj += ':{}'.format(format_spec)
result.append('{' + obj + '}')
else:
used_args.add(arg_used)
# do any conversion on the resulting object
obj = self.convert_field(obj, conversion)
# expand the format spec, if needed
format_spec, auto_arg_index = self._vformat(
format_spec, args, kwargs,
used_args, recursion_depth-1,
auto_arg_index=auto_arg_index)
# format the object and append to the result
result.append(self.format_field(obj, format_spec))
return ''.join(result), auto_arg_index
def sformat(s, *args, **kwargs):
# type: (str, *Any, **Any) -> str
"""
Sparse format a string.
Parameters
----------
s : str
args : *Any
kwargs : **Any
Examples
--------
>>> sformat('The {} is {}', 'answer')
'The answer is {}'
>>> sformat('The answer to {question!r} is {answer:0.2f}', answer=42)
'The answer to {question!r} is 42.00'
>>> sformat('The {} to {} is {:0.{p}f}', 'answer', 'everything', p=4)
'The answer to everything is {:0.4f}'
Returns
-------
str
"""
return SparseFormatter().format(s, *args, **kwargs)
在编写测试以确定该方法的行为方式后,我发现各种实施方案存在问题。如果有人发现它们有用,下面是这些测试。
import pytest
def test_auto_indexing():
# test basic arg auto-indexing
assert sformat('{}{}', 4, 2) == '42'
assert sformat('{}{} {}', 4, 2) == '42 {}'
def test_manual_indexing():
# test basic arg indexing
assert sformat('{0}{1} is not {1} or {0}', 4, 2) == '42 is not 2 or 4'
assert sformat('{0}{1} is {3} {1} or {0}', 4, 2) == '42 is {3} 2 or 4'
def test_mixing_manualauto_fails():
# test mixing manual and auto args raises
with pytest.raises(ValueError):
assert sformat('{!r} is {0}{1}', 4, 2)
def test_kwargs():
# test basic kwarg
assert sformat('{base}{n}', base=4, n=2) == '42'
assert sformat('{base}{n}', base=4, n=2, extra='foo') == '42'
assert sformat('{base}{n} {key}', base=4, n=2) == '42 {key}'
def test_args_and_kwargs():
# test mixing args/kwargs with leftovers
assert sformat('{}{k} {v}', 4, k=2) == '42 {v}'
# test mixing with leftovers
r = sformat('{}{} is the {k} to {!r}', 4, 2, k='answer')
assert r == '42 is the answer to {!r}'
def test_coercion():
# test coercion is preserved for skipped elements
assert sformat('{!r} {k!r}', '42') == "'42' {k!r}"
def test_nesting():
# test nesting works with or with out parent keys
assert sformat('{k:>{size}}', k=42, size=3) == ' 42'
assert sformat('{k:>{size}}', size=3) == '{k:>3}'
@pytest.mark.parametrize(
('s', 'expected'),
[
('{a} {b}', '1 2.0'),
('{z} {y}', '{z} {y}'),
('{a} {a:2d} {a:04d} {y:2d} {z:04d}', '1 1 0001 {y:2d} {z:04d}'),
('{a!s} {z!s} {d!r}', '1 {z!s} {\'k\': \'v\'}'),
('{a!s:>2s} {z!s:>2s}', ' 1 {z!s:>2s}'),
('{a!s:>{a}s} {z!s:>{z}s}', '1 {z!s:>{z}s}'),
('{a.imag} {z.y}', '0 {z.y}'),
('{e[0]:03d} {z[0]:03d}', '042 {z[0]:03d}'),
],
ids=[
'normal',
'none',
'formatting',
'coercion',
'formatting+coercion',
'nesting',
'getattr',
'getitem',
]
)
def test_sformat(s, expected):
# test a bunch of random stuff
data = dict(
a=1,
b=2.0,
c='3',
d={'k': 'v'},
e=[42],
)
assert expected == sformat(s, **data)
对我来说,这已经足够好了:
>>> 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'