định dạng chuỗi một phần


128

Có thể thực hiện định dạng chuỗi một phần với các phương thức định dạng chuỗi nâng cao, tương tự như mẫu chuỗi safe_substitute() chức năng không?

Ví dụ:

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

Câu trả lời:


58

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.


7
Điều này không hoạt động đối với các định dạng nâng cao hơn như{bar:1.2f}
MaxNoe

Tôi hiểu rằng "việc triển khai cơ bản nhất chỉ hoạt động chính xác cho các trường hợp cơ bản" nhưng có cách nào để mở rộng điều này để thậm chí không xóa thông số định dạng không?
Tadhg McDonald-Jensen

5
@ TadhgMcDonald-Jensen: Vâng, có một cách. Thay vì trả về một chuỗi __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
Sven Marnach

@SvenMarnach tại sao bằng chứng về khái niệm của bạn không có trong câu trả lời của bạn? Đó là một chút khó nắm bắt. Có bất kỳ cảnh báo được biết ngăn cản bạn quảng bá nó?
norok2

1
@ norok2 Đó là một câu trả lời cho một câu hỏi được hỏi trong một bình luận, vì vậy tôi đặt câu trả lời trong một bình luận. Câu hỏi ban đầu không thực sự bao gồm yêu cầu đó và tôi thường vẫn nghĩ rằng hơi lạ khi cố gắng định dạng một phần chuỗi.
Sven Marnach

128

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 foobarđồng thời - bạn phải thực hiện tuần tự.


Điểm này là gì? Nếu tôi chỉ định cả foo và bar: 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ó?
n611x007

10
Rằng bạn không thể điền cả hai cùng một lúc là phiền phức. Điều này hữu ích khi, vì bất kỳ lý do gì, bạn phải định dạng chuỗi của mình theo các giai đoạn và bạn biết thứ tự của các giai đoạn đó.
aaren

1
Bạn có thể nên thiết kế theo cách của bạn khỏi phải làm điều này, nhưng có lẽ bạn buộc phải làm vậy.
aaren

2
Không biết về điều này. Tôi đã có một vài trường hợp sử dụng mà tôi muốn "tố" một chuỗi dưới dạng một mẫu nhỏ
ejrb

Điều này cực kỳ hữu ích khi điền một phần của chuỗi vào một phần của mã của bạn, nhưng để lại một trình giữ chỗ sẽ được điền sau đó trong một phần khác của mã.
Alex Petralia

98

Bạn có thể sử dụng partialchứ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

2
Không chỉ là giải pháp ngắn nhất và dễ đọc nhất, mà còn mô tả ý định của lập trình viên. Phiên bản Python3: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 đúng, câu trả lời cần một số tình yêu;)
ypercubeᵀᴹ

8
@ ypercubeᵀᴹ Chà, tôi không chắc đây là chính xác những gì hầu hết mọi người đang tìm kiếm. 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}").
Delgan

1
Điều này tốt hơn cho trường hợp khi bạn hoạt động với đầu vào mà bạn không kiểm soát 100%. Hãy tưởng tượng: "{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".
Benjamin Manns

50

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 Formatterlớ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)

29

Không chắc chắn nếu điều này là ok như một cách giải quyết nhanh chóng, nhưng làm thế nào về

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

? :)


Tôi hoàn toàn làm như vậy, ước gì tôi biết nếu có sự cẩn thận khi làm như vậy.
ramgo

11

Nếu bạn xác định phương thức Formatterghi đè get_valuephươ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ạ bartới "{bar}"nếu barkhô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 .


Có vẻ như một tính năng python> = 2.6.
n611x007

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

Thử thứ này đi.


Wow, chính xác những gì tôi cần! Bạn sẽ giải thích nó?
Serge Chizhik

1
{{}}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ới {, và }, tương ứng.
7yl4r

Vấn đề của giải pháp này là nhân đôi {{ }}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_idgiá trị.
Franzi

7

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

Có vẻ như một tính năng python> = 2.6.
n611x007

Tôi chắc chắn đang sử dụng giải pháp này :) Cảm ơn!
astrojuanlu

2
Xin lưu ý rằng điều này sẽ làm mất thông số chuyển đổi và định dạng nếu chúng tồn tại (và nó thực sự áp dụng thông số định dạng cho giá trị được trả về. Tức là ( {field!s: >4}trở thành{field}
Brendan Abel

3

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

3

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}'
  • cung cấp giao diện tương tự như str.format(không chỉ là ánh xạ)
  • hỗ trợ các tùy chọn định dạng phức tạp hơn:
    • cưỡng chế {k!s} {!r}
    • làm tổ {k:>{size}}
    • nhận được {k.foo}
    • getitem {k[0]}
    • ép buộc + định dạng {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)

Tôi đã thêm một câu trả lời tương tự như mã @SvenMarnach nhưng xử lý cưỡng chế chính xác cho các bài kiểm tra của bạn.
Tohiko

1

Đề 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 dictvà quá tải __missing__(self, key)) được hiển thị tại đây: https://stackoverflow.com/a/17215533/333403


0

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'

0

Có một cách nữa để đạt được điều này tức là sử dụng format%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'

0

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 tmpllà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.


0

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:

  1. tuân thủ nghiêm ngặt cú pháp được công nhận bởi str.format_map()mẫu;
  2. có thể giữ lại định dạng phức tạp, tức là hỗ trợ đầy đủ Định dạng Ngôn ngữ nhỏ

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ở đó ):

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)

Lưu ý rằng đó {d[x]}không phải là một chuỗi định dạng hợp lệ theo như tôi biết.
Sven Marnach

@SvenMarnach Các tài liệu chính thức nói rõ ràng field_name ::= arg_name ("." attribute_name | "[" element_index "]")*và cả hai str.format()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ệ.
norok2

Bạn có thể đưa ra một ví dụ về việc sử dụng 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.
Sven Marnach

@SvenMarnach a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))giúp bạn '' YAY! '
norok2

1
Ah tôi thấy. Tôi đã giả sử điều này được diễn giải như a[b]trong mã Python, nhưng thực ra đó là a["b"]Cảm ơn!
Sven Marnach

0

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 settrong 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 argstừ đ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}'

0

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)

0

Nếu bạn đang thực hiện nhiều chức năng tạo khuôn mẫu và tìm thấy chức năng tạo khuôn mẫu chuỗi tích hợp của Python là không đủ hoặc cồng kềnh, hãy xem Jinja2 .

Từ các tài liệu:

Jinja là một ngôn ngữ tạo khuôn mẫu hiện đại và thân thiện với Python, được mô phỏng theo các mẫu của Django.


0

Đọc bình luận @Sam Bourne, tôi đã sửa đổi 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_mapsafe_format_mapdự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>

-2

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'

Bạn đang thay thế {foo} bằng một chuỗi trống. Câu hỏi là về định dạng một phần để định dạng cuối cùng, không bỏ qua các trường bị thiếu.
egvo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.