เป็นไปได้ไหมที่จะจัดรูปแบบสตริงบางส่วนด้วยวิธีการจัดรูปแบบสตริงขั้นสูงซึ่งคล้ายกับ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'
คำตอบ:
คุณสามารถหลอกล่อเป็นการจัดรูปแบบบางส่วนโดยเขียนทับการแมป:
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}
แน่นอนว่าการใช้งานขั้นพื้นฐานนี้ใช้ได้กับกรณีพื้นฐานเท่านั้น
__missing__()
ให้ส่งคืนอินสแตนซ์ของคลาสแบบกำหนดเองที่แทนที่__format__()
ด้วยวิธีการส่งคืนตัวยึดตำแหน่งเดิมรวมถึงข้อกำหนดรูปแบบ หลักฐานแนวคิด: ideone.com/xykV7R
หากคุณทราบว่าคุณกำลังจัดรูปแบบสิ่งต่างๆตามลำดับ:
s = '{foo} {{bar}}'
ใช้แบบนี้:
ss = s.format(foo='FOO')
print ss
>>> 'FOO {bar}'
print ss.format(bar='BAR')
>>> 'FOO BAR'
คุณไม่สามารถระบุfoo
และbar
ในเวลาเดียวกันได้ - คุณต้องทำตามลำดับ
s.format(foo='FOO',bar='BAR')
ฉันก็ยังได้'FOO {bar}'
ไม่ว่าจะเกิดอะไรขึ้น คุณช่วยชี้แจงได้ไหม
คุณสามารถใช้partial
ฟังก์ชันfunctools
ที่สั้นอ่านได้มากที่สุดและยังอธิบายถึงความตั้งใจของผู้เขียนโค้ด:
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()
จะไม่ช่วยฉันถ้าฉันต้องทำการประมวลผลด้วยสตริงที่จัดรูปแบบบางส่วน (นั่นคือ"FOO {bar}"
)
"{foo} {{bar}}".format(foo="{bar}").format(bar="123")
จากตัวอย่างอื่น ๆ ฉันคาดหวัง"{bar} 123"
แต่มันจะออก"123 123"
มา
ข้อ จำกัด ของ.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}')
? :)
หากคุณกำหนดวิธีการของคุณเองFormatter
ซึ่งจะแทนที่get_value
เมธอดคุณสามารถใช้เพื่อแมปชื่อฟิลด์ที่ไม่ได้กำหนดกับสิ่งที่คุณต้องการ:
http://docs.python.org/library/string.html#string.Formatter.get_value
ตัวอย่างเช่นคุณสามารถ map bar
ไป"{bar}"
ถ้าbar
ไม่ได้อยู่ใน kwargs
อย่างไรก็ตามต้องใช้format()
วิธีการของวัตถุ Formatter ของคุณไม่ใช่format()
วิธีการของสตริง
>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)
'fd:123:{topic_id}'
ลองดูสิ
{{
และ}}
เป็นวิธีหนึ่งในการหลีกเลี่ยงเครื่องหมายการจัดรูปแบบดังนั้นจึงformat()
ไม่ทำการแทนที่และแทนที่{{
และ}}
ด้วย{
และ}
ตามลำดับ
{{ }}
{}
อดีต 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)
จะส่งคืนข้อผิดพลาดเนื่องจากรูปแบบที่สองไม่ได้ให้topic_id
ค่า
ขอบคุณความคิดเห็นของแอมเบอร์ทำให้ฉันได้สิ่งนี้:
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}
สำหรับฉันสิ่งนี้ดีพอ:
>>> 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'
โซลูชันทั้งหมดที่ฉันพบดูเหมือนจะมีปัญหาเกี่ยวกับข้อมูลจำเพาะขั้นสูงหรือตัวเลือกการแปลง @ 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)
คำแนะนำของฉันจะเป็นดังต่อไปนี้ (ทดสอบด้วย 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'
อัปเดต:
วิธีที่หรูหรายิ่งขึ้น (คลาสย่อยdict
และการโอเวอร์โหลด__missing__(self, key)
) แสดงอยู่ที่นี่: https://stackoverflow.com/a/17215533/333403
สมมติว่าคุณจะไม่ใช้สตริงจนกว่าจะกรอกข้อมูลครบถ้วนคุณสามารถทำสิ่งต่างๆเช่นคลาสนี้:
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'
มีอีกวิธีหนึ่งที่จะบรรลุเช่นนี้โดยใช้format
และ%
แทนที่ตัวแปร ตัวอย่างเช่น:
>>> s = '{foo} %(bar)s'
>>> s = s.format(foo='my_foo')
>>> s
'my_foo %(bar)s'
>>> s % {'bar': 'my_bar'}
'my_foo my_bar'
วิธีแก้ปัญหาที่น่าเกลียดมาก แต่ง่ายที่สุดสำหรับฉันคือทำ:
tmpl = '{foo}, {bar}'
tmpl.replace('{bar}', 'BAR')
Out[3]: '{foo}, BAR'
ด้วยวิธีนี้คุณยังคงสามารถใช้tmpl
เป็นเทมเพลตทั่วไปและทำการจัดรูปแบบบางส่วนเมื่อจำเป็นเท่านั้น ฉันพบว่าปัญหานี้เป็นเรื่องเล็กน้อยเกินกว่าที่จะใช้วิธีแก้ปัญหาที่มากเกินไปเช่น Mohan Raj's
หลังจากทดสอบโซลูชันที่มีแนวโน้มมากที่สุดจากที่นี่และที่นั่นฉันตระหนักว่าไม่มีข้อใดตรงตามข้อกำหนดต่อไปนี้:
str.format_map()
ที่แม่แบบยอมรับอย่างเคร่งครัดดังนั้นฉันจึงเขียนวิธีแก้ปัญหาของตัวเองซึ่งตรงตามข้อกำหนดข้างต้น ( แก้ไข : ตอนนี้เป็นเวอร์ชันโดย @SvenMarnach - ตามที่รายงานในคำตอบนี้ - ดูเหมือนว่าจะจัดการกับกรณีมุมที่ฉันต้องการ)
โดยทั่วไปฉันจบลงด้วยการแยกสตริงแม่แบบในการหาจับคู่ซ้อน{.*?}
กลุ่ม (โดยใช้find_all()
ฟังก์ชั่นผู้ช่วย) และการสร้างสตริงในรูปแบบที่มีความก้าวหน้าและได้โดยตรงโดยใช้ขณะที่จับที่อาจเกิดขึ้นstr.format_map()
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
(รหัสนี้มีอยู่ในFlyingCircus ด้วย - การปฏิเสธความรับผิด: ฉันเป็นผู้เขียนหลัก)
การใช้งานสำหรับรหัสนี้จะเป็น:
print(safe_format_map('{a} {b} {c}', dict(a=-A-)))
# -A- {b} {c}
ลองเปรียบเทียบสิ่งนี้กับโซลูชันที่ฉันชอบ (โดย @SvenMarnach ที่กรุณาแบ่งปันรหัสของเขาที่นี่และที่นั่น ):
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}')
ที่เกิดขึ้นใน:
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}
อย่างที่คุณเห็นเวอร์ชันที่อัปเดตในขณะนี้ดูเหมือนว่าจะจัดการกับกรณีมุมที่เวอร์ชันก่อนหน้านี้ใช้ไม่ได้
Timewise อยู่ในระยะประมาณ 50% ของกันและกันขึ้นอยู่กับtext
รูปแบบที่แท้จริง(และน่าจะเป็นของจริงsource
) แต่safe_format_map()
ดูเหมือนว่าจะมีข้อได้เปรียบในการทดสอบส่วนใหญ่ที่ฉันทำ (แน่นอนว่าพวกเขาหมายถึงอะไร):
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]}
ไม่ใช่สตริงรูปแบบที่ถูกต้องเท่าที่ฉันทราบ
field_name ::= arg_name ("." attribute_name | "[" element_index "]")*
และทั้งสองstr.format()
และstr.format_map()
เข้าใจมัน ฉันบอกว่ามีหลักฐานเพียงพอว่านี่เป็นสตริงรูปแบบที่ถูกต้อง
str.format()
กับดัชนีที่ไม่ใช่จำนวนเต็มในวงเล็บเหลี่ยมได้หรือไม่? ฉันสามารถทำให้ดัชนีจำนวนเต็มใช้ได้เท่านั้น
a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))
ทำให้คุณ `` YAY! '
a[b]
ในรหัส Python แต่จริงๆแล้วก็a["b"]
ขอบคุณ!
หากคุณต้องการที่จะแกะพจนานุกรมที่จะผ่านการขัดแย้งกับformat
, ในขณะที่คำถามที่เกี่ยวข้องนี้คุณสามารถใช้วิธีการดังต่อไปนี้
ขั้นแรกให้ถือว่าสตริงs
เหมือนกับในคำถามนี้:
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'
อย่างไรก็ตามก่อนอื่นคุณสามารถรับset
อาร์กิวเมนต์ที่ตั้งชื่อทั้งหมดจากs
และสร้างพจนานุกรมที่จับคู่อาร์กิวเมนต์กับตัวมันเองที่ห่อด้วยวงเล็บปีกกา:
from string import Formatter
args = {x[1]:'{'+x[1]+'}' for x in Formatter().parse(s)}
print(args)
#{'foo': '{foo}', 'bar': '{bar}'}
ตอนนี้ใช้พจนานุกรมการกรอกข้อมูลในกุญแจที่หายไปargs
replacements
สำหรับ python 3.5+ คุณสามารถทำได้ในนิพจน์เดียว :
new_s = s.format(**{**args, **replacements}}
print(new_s)
#'FOO {bar}'
สำหรับ python เวอร์ชันเก่าคุณสามารถโทรupdate
:
args.update(replacements)
print(s.format(**args))
#'FOO {bar}'
ฉันชอบคำตอบของ @ sven-marnach คำตอบของฉันเป็นเพียงเวอร์ชันขยาย อนุญาตให้จัดรูปแบบที่ไม่ใช่คำหลักและละเว้นคีย์พิเศษ นี่คือตัวอย่างการใช้งาน (ชื่อของฟังก์ชันอ้างอิงถึงการจัดรูปแบบสตริง f python 3.6):
# 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)
อ่านความคิดเห็น @ Sam บอร์นผมปรับเปลี่ยน @ SvenMarnach ของรหัส
ในการทำงานอย่างถูกต้องกับการบังคับ (ชอบ{a!s:>2s}
) โดยไม่ต้องเขียน parser ที่กำหนดเอง แนวคิดพื้นฐานไม่ใช่การแปลงเป็นสตริง แต่เชื่อมต่อคีย์ที่ขาดหายไปด้วยแท็กการบังคับ
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)
ใช้ (ตัวอย่าง) เช่นนี้
SafeFormatter().format("{a:<5} {b:<10}", a=10)
การทดสอบต่อไปนี้ (ได้รับแรงบันดาลใจจากการทดสอบจาก @ norok2) ตรวจสอบผลลัพธ์สำหรับแบบดั้งเดิมformat_map
และsafe_format_map
ตามคลาสข้างต้นในสองกรณี: ระบุคีย์เวิร์ดที่ถูกต้องหรือไม่มี
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')
เอาต์พุตใด
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 print_foo_bar(foo='', bar=''):
s = '{foo} {bar}'
return s.format(foo=foo, bar=bar)
print_foo_bar(bar='BAR') # ' BAR'
{bar:1.2f}