Câu trả lời:
Bạn có thể lừa nó thành định dạng một phần bằng cách ghi đè ánh xạ:
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))
in ấn
FOO {bar}
Tất nhiên việc thực hiện cơ bản này chỉ hoạt động chính xác cho các trường hợp cơ bản.
__missing__()
, trả về một thể hiện của lớp tùy chỉnh ghi đè __format__()
theo cách để trả về trình giữ chỗ ban đầu bao gồm cả đặc tả định dạng. Bằng chứng về khái niệm: ideone.com/xykV7R
Nếu bạn biết bạn đang định dạng thứ tự theo thứ tự nào:
s = '{foo} {{bar}}'
Sử dụng nó như thế này:
ss = s.format(foo='FOO')
print ss
>>> 'FOO {bar}'
print ss.format(bar='BAR')
>>> 'FOO BAR'
Bạn không thể chỉ định foo
và bar
đồng thời - bạn phải thực hiện tuần tự.
s.format(foo='FOO',bar='BAR')
thì tôi vẫn nhận được 'FOO {bar}'
, không có vấn đề gì. Bạn có thể làm rõ nó?
Bạn có thể sử dụng partial
chức năng từ functools
đó ngắn gọn, dễ đọc nhất và cũng mô tả ý định của người viết mã:
from functools import partial
s = partial("{foo} {bar}".format, foo="FOO")
print s(bar="BAR")
# FOO BAR
python from functool 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
partial()
sẽ không giúp tôi nếu tôi cần thực hiện một số xử lý với chuỗi được định dạng một phần (đó là "FOO {bar}"
).
"{foo} {{bar}}".format(foo="{bar}").format(bar="123")
từ các ví dụ khác. Tôi mong đợi "{bar} 123"
nhưng họ đầu ra "123 123"
.
Hạn chế này .format()
- không có khả năng thay thế một phần - đã làm tôi khó chịu.
Sau khi đánh giá việc viết một Formatter
lớp tùy chỉnh như được mô tả trong nhiều câu trả lời ở đây và thậm chí xem xét sử dụng các gói của bên thứ ba như lazy_format , tôi đã phát hiện ra một giải pháp sẵn có đơn giản hơn nhiều: Chuỗi mẫu
Nó cung cấp chức năng tương tự nhưng cũng cung cấp safe_substitute()
phương pháp kỹ lưỡng thay thế một phần . Các chuỗi mẫu cần phải có $
tiền tố (cảm thấy hơi kỳ lạ - nhưng giải pháp tổng thể tôi nghĩ là tốt hơn).
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'
Hình thành một trình bao bọc tiện lợi dựa trên điều này:
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
Tương tự như một trình bao bọc dựa trên câu trả lời của Sven sử dụng định dạng chuỗi mặc định:
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)
Nếu bạn xác định phương thức Formatter
ghi đè get_value
phương thức của riêng bạn, bạn có thể sử dụng phương thức đó để ánh xạ tên trường không xác định thành bất cứ điều gì bạn muốn:
http://docs.python.org/l Library / chuỗi.html # String.Formatter.get_value
Chẳng hạn, bạn có thể ánh xạ bar
tới "{bar}"
nếu bar
không có trong kwargs.
Tuy nhiên, điều đó đòi hỏi phải sử dụng format()
phương thức của đối tượng Formatter của bạn chứ không phải format()
phương thức của chuỗi .
>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)
'fd:123:{topic_id}'
Thử thứ này đi.
{{
và }}
là một cách để thoát các dấu định dạng, do format()
đó không thực hiện thay thế và thay thế {{
và }}
với {
, và }
, tương ứng.
{{ }}
chỉ hoạt động với một định dạng, nếu bạn cần áp dụng nhiều hơn, bạn sẽ cần thêm nhiều hơn {}
. Ví dụ. 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)
sẽ trả về lỗi vì định dạng thứ hai không cung cấp topic_id
giá trị.
Nhờ bình luận của Amber , tôi đã nghĩ ra điều này:
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}
trở thành{field}
Đối với tôi điều này là đủ tốt:
>>> 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'
Tất cả các giải pháp tôi tìm thấy dường như có vấn đề với các tùy chọn chuyển đổi hoặc thông số kỹ thuật nâng cao hơn. @ SvenMarnach của FormatPlaceholder là tuyệt vời thông minh nhưng nó không hoạt động đúng với sự ép buộc (ví dụ {a!s:>2s}
) vì nó gọi là __str__
phương pháp (trong ví dụ này) thay vì__format__
và bạn mất bất kỳ định dạng bổ sung.
Đây là những gì tôi đã kết thúc và một số tính năng chính của nó:
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
(không chỉ là ánh xạ){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)
Tôi đã phát hiện ra các vấn đề với các triển khai khác nhau sau khi viết một số thử nghiệm về cách tôi muốn phương pháp này hoạt động. Họ ở dưới nếu bất cứ ai thấy họ sâu sắc.
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)
Đề xuất của tôi sẽ là như sau (đã thử nghiệm với Python3.6):
class Lazymap(object):
def __init__(self, **kwargs):
self.dict = kwargs
def __getitem__(self, key):
return self.dict.get(key, "".join(["{", key, "}"]))
s = '{foo} {bar}'
s.format_map(Lazymap(bar="FOO"))
# >>> '{foo} FOO'
s.format_map(Lazymap(bar="BAR"))
# >>> '{foo} BAR'
s.format_map(Lazymap(bar="BAR", foo="FOO", baz="BAZ"))
# >>> 'FOO BAR'
Cập nhật:
Một cách thậm chí thanh lịch hơn (phân lớp dict
và quá tải __missing__(self, key)
) được hiển thị tại đây: https://stackoverflow.com/a/17215533/333403
Giả sử bạn sẽ không sử dụng chuỗi cho đến khi nó hoàn toàn được điền, bạn có thể làm một cái gì đó giống như lớp này:
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)
Thí dụ:
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'
Có một cách nữa để đạt được điều này tức là sử dụng format
và %
thay thế các biến. Ví dụ:
>>> s = '{foo} %(bar)s'
>>> s = s.format(foo='my_foo')
>>> s
'my_foo %(bar)s'
>>> s % {'bar': 'my_bar'}
'my_foo my_bar'
Một giải pháp rất xấu nhưng đơn giản nhất đối với tôi là chỉ cần làm:
tmpl = '{foo}, {bar}'
tmpl.replace('{bar}', 'BAR')
Out[3]: '{foo}, BAR'
Bằng cách này, bạn vẫn có thể sử dụng tmpl
làm mẫu thông thường và chỉ thực hiện định dạng một phần khi cần. Tôi thấy vấn đề này quá tầm thường khi sử dụng giải pháp quá mức như của Mohan Raj.
Sau khi thử nghiệm các giải pháp hứa hẹn nhất từ đây đến đó , tôi nhận ra rằng không ai trong số họ thực sự đáp ứng các yêu cầu sau:
str.format_map()
mẫu;Vì vậy, tôi đã viết giải pháp của riêng mình, đáp ứng các yêu cầu trên. ( EDIT : bây giờ là phiên bản của @SvenMarnach - như được báo cáo trong câu trả lời này - dường như xử lý các trường hợp góc tôi cần).
Về cơ bản, tôi đã kết thúc phân tích chuỗi mẫu, tìm các {.*?}
nhóm lồng nhau phù hợp (sử dụng find_all()
hàm trợ giúp) và xây dựng chuỗi được định dạng liên tục và trực tiếp sử dụng str.format_map()
trong khi nắm bắt bất kỳ tiềm năng nào KeyError
.
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
def matching_delimiters(
text,
l_delim,
r_delim,
including=True):
"""
Find matching delimiters in a sequence.
The delimiters are matched according to nesting level.
Args:
text (str|bytes|bytearray): The input text.
l_delim (str|bytes|bytearray): The left delimiter.
r_delim (str|bytes|bytearray): The right delimiter.
including (bool): Include delimeters.
yields:
result (tuple[int]): The matching delimiters.
"""
l_offset = len(l_delim) if including else 0
r_offset = len(r_delim) if including else 0
stack = []
l_tokens = set(find_all(text, l_delim))
r_tokens = set(find_all(text, r_delim))
positions = l_tokens.union(r_tokens)
for pos in sorted(positions):
if pos in l_tokens:
stack.append(pos + 1)
elif pos in r_tokens:
if len(stack) > 0:
prev = stack.pop()
yield (prev - l_offset, pos + r_offset, len(stack))
else:
raise ValueError(
'Found `{}` unmatched right token(s) `{}` (position: {}).'
.format(len(r_tokens) - len(l_tokens), r_delim, pos))
if len(stack) > 0:
raise ValueError(
'Found `{}` unmatched left token(s) `{}` (position: {}).'
.format(
len(l_tokens) - len(r_tokens), l_delim, stack.pop() - 1))
def safe_format_map(
text,
source):
"""
Perform safe string formatting from a mapping source.
If a value is missing from source, this is simply ignored, and no
`KeyError` is raised.
Args:
text (str): Text to format.
source (Mapping|None): The mapping to use as source.
If None, uses caller's `vars()`.
Returns:
result (str): The formatted text.
"""
stack = []
for i, j, depth in matching_delimiters(text, '{', '}'):
if depth == 0:
try:
replacing = text[i:j].format_map(source)
except KeyError:
pass
else:
stack.append((i, j, replacing))
result = ''
i, j = len(text), 0
while len(stack) > 0:
last_i = i
i, j, replacing = stack.pop()
result = replacing + text[j:last_i] + result
if i > 0:
result = text[0:i] + result
return result
(Mã này cũng có sẵn trong FlyingCircus - TUYÊN BỐ: Tôi là tác giả chính của nó.)
Việc sử dụng mã này sẽ là:
print(safe_format_map('{a} {b} {c}', dict(a=-A-)))
# -A- {b} {c}
Hãy so sánh giải pháp này với giải pháp yêu thích của tôi (bởi @SvenMarnach, người vui lòng chia sẻ mã của mình ở đây và ở đó ):
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))
Dưới đây là một vài bài kiểm tra:
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=[])
và mã để làm cho nó chạy:
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}')
dẫn đến:
str.format_map : OK : --- {'g': 'Oh yes!'}
safe_format_map : OK : {b} {f}
safe_format_alt : OK : {b} {f}
str.format_map : OK : 4 ---
safe_format_map : OK : 4 {b}
safe_format_alt : OK : 4 {b}
str.format_map : OK : 4 --- 101
safe_format_map : OK : 4 {b} 101
safe_format_alt : OK : 4 {b} 101
str.format_map : OK : 4 --- 101
safe_format_map : OK : 4 {b} 101
safe_format_alt : OK : 4 {b} 101
str.format_map : OK : 4 --- 101
safe_format_map : OK : 4 {b} 101
safe_format_alt : OK : 4 {b} 101
str.format_map : OK : 4 --- 0101
safe_format_map : OK : 4 {b} 0101
safe_format_alt : OK : 4 {b} 0101
str.format_map : OK : 4 --- FOO
safe_format_map : OK : 4 {b} FOO
safe_format_alt : OK : 4 {b} FOO
str.format_map : OK : 4 --- <built-in method index of list object at 0x7f7a485666c8>
safe_format_map : OK : 4 {b} <built-in method index of list object at 0x7f7a485666c8>
safe_format_alt : OK : 4 {b} <built-in method index of list object at 0x7f7a485666c8>
str.format_map : OK : 4 --- Oh yes!
safe_format_map : OK : 4 {b} {f[g]}
safe_format_alt : OK : 4 {b} {f[g]}
str.format_map : OK : 4 --- <built-in method values of dict object at 0x7f7a485da090>
safe_format_map : OK : 4 {b} {f.values}
safe_format_alt : OK : 4 {b} {f.values}
như bạn có thể thấy, phiên bản cập nhật bây giờ dường như xử lý tốt các trường hợp góc mà phiên bản trước đó từng bị lỗi.
Theo thời gian, họ là trong khoảng. 50% của nhau, tùy thuộc vào text
định dạng thực tế (và có thể là thực tế source
), nhưng safe_format_map()
dường như có một lợi thế trong hầu hết các thử nghiệm tôi đã thực hiện (tất nhiên chúng có nghĩa là gì):
for text in test_texts:
print(f' {text}')
%timeit safe_format(text * 1000, source)
%timeit safe_format_alt(text * 1000, source)
{b} {f}
3.93 ms ± 153 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
6.35 ms ± 51.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b}
4.37 ms ± 57.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
5.2 ms ± 159 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {c:5d}
7.15 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.76 ms ± 69.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {c!s}
7.04 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.56 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {c!s:>{a}s}
8.91 ms ± 113 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10.5 ms ± 181 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {c:0{a}d}
8.84 ms ± 147 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10.2 ms ± 202 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {d[x]}
7.01 ms ± 197 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
7.35 ms ± 106 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {e.index}
11 ms ± 68.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.78 ms ± 405 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {f[g]}
6.55 ms ± 88.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
9.12 ms ± 159 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{a} {b} {f.values}
6.61 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
9.92 ms ± 98.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
{d[x]}
không phải là một chuỗi định dạng hợp lệ theo như tôi biết.
field_name ::= arg_name ("." attribute_name | "[" element_index "]")*
và cả hai str.format()
và str.format_map()
hiểu nó. Tôi muốn nói có đủ bằng chứng cho việc đây là một chuỗi định dạng hợp lệ.
str.format()
với chỉ mục không nguyên trong ngoặc vuông không? Tôi chỉ có thể làm cho các chỉ số nguyên hoạt động.
a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))
giúp bạn '' YAY! '
a[b]
trong mã Python, nhưng thực ra đó là a["b"]
Cảm ơn!
Nếu bạn muốn giải nén một từ điển để truyền đối số cho format
, như trong câu hỏi liên quan này , bạn có thể sử dụng phương pháp sau.
Đầu tiên giả sử chuỗi s
giống như trong câu hỏi này:
s = '{foo} {bar}'
và các giá trị được đưa ra bởi từ điển sau:
replacements = {'foo': 'FOO'}
Rõ ràng điều này sẽ không hoạt động:
s.format(**replacements)
#---------------------------------------------------------------------------
#KeyError Traceback (most recent call last)
#<ipython-input-29-ef5e51de79bf> in <module>()
#----> 1 s.format(**replacements)
#
#KeyError: 'bar'
Tuy nhiên, trước tiên bạn có thể nhận được một set
trong số tất cả các đối số được đặt tên từs
và tạo một từ điển ánh xạ đối số thành chính nó được gói trong các dấu ngoặc nhọn:
from string import Formatter
args = {x[1]:'{'+x[1]+'}' for x in Formatter().parse(s)}
print(args)
#{'foo': '{foo}', 'bar': '{bar}'}
Bây giờ sử dụng args
từ điển để điền vào các phím bị thiếu replacements
. Đối với python 3.5+, bạn có thể thực hiện việc này trong một biểu thức :
new_s = s.format(**{**args, **replacements}}
print(new_s)
#'FOO {bar}'
Đối với các phiên bản cũ hơn của python, bạn có thể gọi update
:
args.update(replacements)
print(s.format(**args))
#'FOO {bar}'
Tôi thích câu trả lời @ sven-marnach. Câu trả lời của tôi chỉ đơn giản là một phiên bản mở rộng của nó. Nó cho phép định dạng không từ khóa và bỏ qua các khóa bổ sung. Dưới đây là các ví dụ về cách sử dụng (tên của hàm là tham chiếu đến định dạng chuỗi python 3.6 f):
# 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}'
Và đây là mã của tôi:
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)
Đọc bình luận @Sam Bourne, tôi đã sửa đổi mã của @ SvenMarnach
để hoạt động chính xác với sự ép buộc (như {a!s:>2s}
) mà không cần viết một trình phân tích cú pháp tùy chỉnh. Ý tưởng cơ bản không phải là chuyển đổi thành chuỗi mà nối các khóa bị thiếu với các thẻ ép buộc.
import string
class MissingKey(object):
def __init__(self, key):
self.key = key
def __str__(self): # Supports {key!s}
return MissingKeyStr("".join([self.key, "!s"]))
def __repr__(self): # Supports {key!r}
return MissingKeyStr("".join([self.key, "!r"]))
def __format__(self, spec): # Supports {key:spec}
if spec:
return "".join(["{", self.key, ":", spec, "}"])
return "".join(["{", self.key, "}"])
def __getitem__(self, i): # Supports {key[i]}
return MissingKey("".join([self.key, "[", str(i), "]"]))
def __getattr__(self, name): # Supports {key.name}
return MissingKey("".join([self.key, ".", name]))
class MissingKeyStr(MissingKey, str):
def __init__(self, key):
if isinstance(key, MissingKey):
self.key = "".join([key.key, "!s"])
else:
self.key = key
class SafeFormatter(string.Formatter):
def __init__(self, default=lambda k: MissingKey(k)):
self.default=default
def get_value(self, key, args, kwds):
if isinstance(key, str):
return kwds.get(key, self.default(key))
else:
return super().get_value(key, args, kwds)
Sử dụng (ví dụ) như thế này
SafeFormatter().format("{a:<5} {b:<10}", a=10)
Các thử nghiệm sau (lấy cảm hứng từ các thử nghiệm từ @ norok2) kiểm tra đầu ra cho truyền thống format_map
và safe_format_map
dựa trên lớp ở trên trong hai trường hợp: cung cấp từ khóa chính xác hoặc không có chúng.
def safe_format_map(text, source):
return SafeFormatter().format(text, **source)
test_texts = (
'{a} ', # simple nothing useful in source
'{a:5d}', # formatting
'{a!s}', # coercion
'{a!s:>{a}s}', # formatting and coercion
'{a:0{a}d}', # nesting
'{d[x]}', # indexing
'{d.values}', # member
)
source = dict(a=10,d=dict(x='FOO'))
funcs = [safe_format_map,
str.format_map
#safe_format_alt # Version based on parsing (See @norok2)
]
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))
print("Testing:", text)
for func in funcs:
try:
print(f'{func.__name__:>{n}s} : OK\t\t\t: ' + func(text, dict()))
except:
print(f'{func.__name__:>{n}s} : FAILED')
try:
print(f'{func.__name__:>{n}s} : OK\t\t\t: ' + func(text, source))
except:
print(f'{func.__name__:>{n}s} : FAILED')
Đầu ra nào
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>
Bạn có thể gói nó trong một hàm có các đối số mặc định:
def print_foo_bar(foo='', bar=''):
s = '{foo} {bar}'
return s.format(foo=foo, bar=bar)
print_foo_bar(bar='BAR') # ' BAR'
{bar:1.2f}