การจัดรูปแบบสตริงบางส่วน


129

เป็นไปได้ไหมที่จะจัดรูปแบบสตริงบางส่วนด้วยวิธีการจัดรูปแบบสตริงขั้นสูงซึ่งคล้ายกับsafe_substitute()ฟังก์ชันเทมเพลตสตริง

ตัวอย่างเช่น:

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

คำตอบ:


58

คุณสามารถหลอกล่อเป็นการจัดรูปแบบบางส่วนโดยเขียนทับการแมป:

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}

แน่นอนว่าการใช้งานขั้นพื้นฐานนี้ใช้ได้กับกรณีพื้นฐานเท่านั้น


7
สิ่งนี้ใช้ไม่ได้กับการจัดรูปแบบขั้นสูงเช่น{bar:1.2f}
MaxNoe

ฉันเข้าใจที่บอกว่า "การใช้งานขั้นพื้นฐานที่สุดจะทำงานได้อย่างถูกต้องสำหรับกรณีพื้นฐานเท่านั้น" แต่มีวิธีใดที่จะขยายให้ถึงแม้จะไม่ลบข้อมูลจำเพาะของรูปแบบ
Tadhg McDonald-Jensen

5
@ TadhgMcDonald-Jensen: ใช่มีวิธี แทนที่จะส่งคืนสตริงใน__missing__()ให้ส่งคืนอินสแตนซ์ของคลาสแบบกำหนดเองที่แทนที่__format__()ด้วยวิธีการส่งคืนตัวยึดตำแหน่งเดิมรวมถึงข้อกำหนดรูปแบบ หลักฐานแนวคิด: ideone.com/xykV7R
Sven Marnach

@SvenMarnach เหตุใดการพิสูจน์แนวคิดของคุณจึงไม่อยู่ในเนื้อหาของคำตอบของคุณ? เป็นเรื่องที่เข้าใจยากเล็กน้อย มีข้อแม้ที่ทราบกันดีว่าป้องกันไม่ให้คุณโปรโมตหรือไม่?
norok2

1
@ norok2 มันเป็นคำตอบสำหรับคำถามที่ถามในความคิดเห็นดังนั้นฉันจึงตอบกลับในความคิดเห็น คำถามเดิมไม่ได้รวมข้อกำหนดนั้นไว้จริงๆและโดยทั่วไปฉันยังคิดว่ามันแปลกนิดหน่อยที่พยายามจัดรูปแบบสตริงบางส่วน
Sven Marnach

128

หากคุณทราบว่าคุณกำลังจัดรูปแบบสิ่งต่างๆตามลำดับ:

s = '{foo} {{bar}}'

ใช้แบบนี้:

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

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

คุณไม่สามารถระบุfooและbarในเวลาเดียวกันได้ - คุณต้องทำตามลำดับ


ประเด็นนี้คืออะไร? ถ้าฉันระบุทั้ง foo และ bar: s.format(foo='FOO',bar='BAR')ฉันก็ยังได้'FOO {bar}'ไม่ว่าจะเกิดอะไรขึ้น คุณช่วยชี้แจงได้ไหม
n611x007

10
การที่คุณไม่สามารถกรอกทั้งสองอย่างพร้อมกันนั้นน่ารำคาญ สิ่งนี้มีประโยชน์เมื่อคุณต้องจัดรูปแบบสตริงเป็นขั้นตอนไม่ว่าด้วยเหตุผลใดและคุณรู้ลำดับขั้นตอนเหล่านั้น
aaren

1
คุณควรออกแบบวิธีที่จะไม่ต้องทำสิ่งนี้ แต่บางทีคุณอาจถูกบังคับให้ทำ
aaren

2
ไม่ทราบเกี่ยวกับเรื่องนี้ ฉันมีกรณีการใช้งานหลายกรณีที่ต้องการ "กำหนด" สตริงเป็นเทมเพลตขนาดเล็ก
ejrb

สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อเติมข้อมูลส่วนหนึ่งของสตริงในส่วนหนึ่งของโค้ดของคุณ แต่ปล่อยให้ตัวยึดตำแหน่งถูกเติมในส่วนอื่นของโค้ดของคุณในภายหลัง
Alex Petralia

99

คุณสามารถใช้partialฟังก์ชันfunctoolsที่สั้นอ่านได้มากที่สุดและยังอธิบายถึงความตั้งใจของผู้เขียนโค้ด:

from functools import partial

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

2
ไม่เพียง แต่เป็นวิธีการแก้ปัญหาที่สั้นและอ่านง่ายที่สุดเท่านั้น แต่ยังอธิบายถึงความตั้งใจของผู้เขียนโค้ดด้วย Python3 version: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
Paul Brown

@PaulBrown จริงคำตอบต้องการความรัก;)
ypercubeᵀᴹ

8
@ ypercubeᵀᴹฉันไม่แน่ใจว่านี่คือสิ่งที่คนส่วนใหญ่กำลังมองหา partial()จะไม่ช่วยฉันถ้าฉันต้องทำการประมวลผลด้วยสตริงที่จัดรูปแบบบางส่วน (นั่นคือ"FOO {bar}")
Delgan

1
กรณีนี้จะดีกว่าเมื่อคุณใช้งานอินพุตที่คุณไม่สามารถควบคุมได้ 100% ลองนึกภาพ: "{foo} {{bar}}".format(foo="{bar}").format(bar="123")จากตัวอย่างอื่น ๆ ฉันคาดหวัง"{bar} 123"แต่มันจะออก"123 123"มา
Benjamin Manns

50

ข้อ จำกัด ของ.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)

29

ไม่แน่ใจว่านี่เป็นวิธีแก้ปัญหาอย่างรวดเร็วหรือไม่ แต่จะทำอย่างไร

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

? :)


ฉันทำแบบเดียวกันทั้งหมดหวังว่าฉันจะรู้ว่ามีข้อแม้ในการทำเช่นนั้นหรือไม่
ramgo

11

หากคุณกำหนดวิธีการของคุณเองFormatterซึ่งจะแทนที่get_valueเมธอดคุณสามารถใช้เพื่อแมปชื่อฟิลด์ที่ไม่ได้กำหนดกับสิ่งที่คุณต้องการ:

http://docs.python.org/library/string.html#string.Formatter.get_value

ตัวอย่างเช่นคุณสามารถ map barไป"{bar}"ถ้าbarไม่ได้อยู่ใน kwargs

อย่างไรก็ตามต้องใช้format()วิธีการของวัตถุ Formatter ของคุณไม่ใช่format()วิธีการของสตริง


ดูเหมือนว่าคุณสมบัติ python> = 2.6
n611x007

11
>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)
'fd:123:{topic_id}'

ลองดูสิ


ว้าวสิ่งที่ฉันต้องการ! คุณช่วยอธิบายได้ไหม
Sergey Chizhik

1
{{และ}}เป็นวิธีหนึ่งในการหลีกเลี่ยงเครื่องหมายการจัดรูปแบบดังนั้นจึงformat()ไม่ทำการแทนที่และแทนที่{{และ}}ด้วย{และ}ตามลำดับ
7yl4r

ปัญหาของการแก้ปัญหานี้คือการที่คู่ทำงานเฉพาะสำหรับรูปแบบหนึ่งถ้าคุณจำเป็นต้องใช้มากขึ้นคุณจะต้องเพิ่มมากขึ้น{{ }} {}อดีต 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)จะส่งคืนข้อผิดพลาดเนื่องจากรูปแบบที่สองไม่ได้ให้topic_idค่า
Franzi

7

ขอบคุณความคิดเห็นของแอมเบอร์ทำให้ฉันได้สิ่งนี้:

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

ดูเหมือนว่าคุณสมบัติ python> = 2.6
n611x007

ฉันใช้วิธีนี้แน่นอน :) ขอบคุณ!
astrojuanlu

2
โปรดทราบว่าสิ่งนี้จะสูญเสียข้อมูลจำเพาะของการแปลงและรูปแบบหากมีอยู่ (และใช้ข้อกำหนดรูปแบบกับค่าที่ส่งคืนจริง ๆ เช่น ( {field!s: >4}กลายเป็น{field}
Brendan Abel

3

สำหรับฉันสิ่งนี้ดีพอ:

>>> 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'

3

โซลูชันทั้งหมดที่ฉันพบดูเหมือนจะมีปัญหาเกี่ยวกับข้อมูลจำเพาะขั้นสูงหรือตัวเลือกการแปลง @ 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}}
    • getattr {k.foo}
    • GetItem {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)

ฉันได้เพิ่มคำตอบที่คล้ายกับรหัส @SvenMarnach แต่จะจัดการกับการบีบบังคับอย่างถูกต้องสำหรับการทดสอบของคุณ
Tohiko

1

คำแนะนำของฉันจะเป็นดังต่อไปนี้ (ทดสอบด้วย 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


0

สมมติว่าคุณจะไม่ใช้สตริงจนกว่าจะกรอกข้อมูลครบถ้วนคุณสามารถทำสิ่งต่างๆเช่นคลาสนี้:

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'

0

มีอีกวิธีหนึ่งที่จะบรรลุเช่นนี้โดยใช้formatและ%แทนที่ตัวแปร ตัวอย่างเช่น:

>>> s = '{foo} %(bar)s'
>>> s = s.format(foo='my_foo')
>>> s
'my_foo %(bar)s'
>>> s % {'bar': 'my_bar'}
'my_foo my_bar'

0

วิธีแก้ปัญหาที่น่าเกลียดมาก แต่ง่ายที่สุดสำหรับฉันคือทำ:

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

ด้วยวิธีนี้คุณยังคงสามารถใช้tmplเป็นเทมเพลตทั่วไปและทำการจัดรูปแบบบางส่วนเมื่อจำเป็นเท่านั้น ฉันพบว่าปัญหานี้เป็นเรื่องเล็กน้อยเกินกว่าที่จะใช้วิธีแก้ปัญหาที่มากเกินไปเช่น Mohan Raj's


0

หลังจากทดสอบโซลูชันที่มีแนวโน้มมากที่สุดจากที่นี่และที่นั่นฉันตระหนักว่าไม่มีข้อใดตรงตามข้อกำหนดต่อไปนี้:

  1. ปฏิบัติตามไวยากรณ์str.format_map()ที่แม่แบบยอมรับอย่างเคร่งครัด
  2. สามารถรักษาการจัดรูปแบบที่ซับซ้อนได้เช่นสนับสนุนFormat Mini-Language อย่างสมบูรณ์

ดังนั้นฉันจึงเขียนวิธีแก้ปัญหาของตัวเองซึ่งตรงตามข้อกำหนดข้างต้น ( แก้ไข : ตอนนี้เป็นเวอร์ชันโดย @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]}ไม่ใช่สตริงรูปแบบที่ถูกต้องเท่าที่ฉันทราบ
Sven Marnach

@SvenMarnach เอกสารอย่างเป็นทางการบอกอย่างชัดเจนfield_name ::= arg_name ("." attribute_name | "[" element_index "]")*และทั้งสองstr.format()และstr.format_map()เข้าใจมัน ฉันบอกว่ามีหลักฐานเพียงพอว่านี่เป็นสตริงรูปแบบที่ถูกต้อง
norok2

คุณสามารถยกตัวอย่างการใช้str.format()กับดัชนีที่ไม่ใช่จำนวนเต็มในวงเล็บเหลี่ยมได้หรือไม่? ฉันสามารถทำให้ดัชนีจำนวนเต็มใช้ได้เท่านั้น
Sven Marnach

@SvenMarnach a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))ทำให้คุณ `` YAY! '
norok2

1
ฉันเห็น ฉันคิดว่าสิ่งนี้ถูกตีความเหมือนa[b]ในรหัส Python แต่จริงๆแล้วก็a["b"]ขอบคุณ!
Sven Marnach

0

หากคุณต้องการที่จะแกะพจนานุกรมที่จะผ่านการขัดแย้งกับ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}'

0

ฉันชอบคำตอบของ @ 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)

0

หากคุณกำลังทำมาก templating และหางูใหญ่ที่สร้างขึ้นในการทำงาน templating สตริงที่จะไม่เพียงพอหรือ clunky, ดูที่Jinja2

จากเอกสาร:

Jinja เป็นภาษาเทมเพลตที่ทันสมัยและเป็นมิตรกับนักออกแบบสำหรับ Python ซึ่งจำลองมาจากเทมเพลตของ Django


0

อ่านความคิดเห็น @ 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>

-2

คุณสามารถรวมไว้ในฟังก์ชันที่รับอาร์กิวเมนต์เริ่มต้น:

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

print_foo_bar(bar='BAR') # ' BAR'

คุณกำลังแทนที่ {foo} ด้วยสตริงว่าง คำถามเกี่ยวกับการจัดรูปแบบบางส่วนสำหรับการจัดรูปแบบขั้นสุดท้ายเพิ่มเติมโดยไม่สนใจช่องที่ขาดหายไป
egvo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.