kısmi dize biçimlendirme


128

Dize şablonu safe_substitute()işlevine benzer şekilde, gelişmiş dizge biçimlendirme yöntemleriyle kısmi dizge biçimlendirmesi yapmak mümkün müdür ?

Örneğin:

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

Yanıtlar:


58

Eşlemenin üzerine yazarak kısmi biçimlendirmeyi kandırabilirsiniz:

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

baskı

FOO {bar}

Elbette bu temel uygulama yalnızca temel durumlarda doğru çalışır.


7
Bu,{bar:1.2f}
MaxNoe

"En temel uygulamanın yalnızca temel durumlar için doğru şekilde çalıştığını" söylemeyi anlıyorum, ancak bunu biçim belirtimini silmemek için genişletmenin bir yolu var mı?
Tadhg McDonald-Jensen

5
@ TadhgMcDonald-Jensen: Evet, bir yolu var. Bir dizeyi içeri döndürmek yerine , biçim belirtimini içeren orijinal yer tutucuyu döndürecek şekilde __missing__()geçersiz kılma özel sınıfının bir örneğini __format__()döndürün. Kavramın kanıtı: ideone.com/xykV7R
Sven Marnach

@SvenMarnach neden kavram kanıtınız cevabınızın gövdesinde yok? Bu biraz zor. Onu tanıtmanızı engelleyen bilinen herhangi bir uyarı var mı?
norok2

1
@ norok2 Bir yorumda sorulan bir sorunun cevabıdır, bu yüzden cevabı bir yoruma ekliyorum. Orijinal soru bu gereksinimi gerçekten içermiyordu ve genel olarak bir dizeyi kısmi biçimlendirmeye çalışmanın biraz tuhaf olduğunu düşünüyorum.
Sven Marnach

128

Öğeleri hangi sırayla biçimlendirdiğinizi biliyorsanız:

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

Bunu şu şekilde kullanın:

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

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

Belirleyemezsiniz foove baraynı zamanda - bunu sırayla yapmanız gerekir.


Bunun anlamı ne? Hem foo hem de bar belirtirsem: ne olursa olsun s.format(foo='FOO',bar='BAR')hala var 'FOO {bar}'. Açıklayabilir misin?
n611x007

10
İkisini de aynı anda dolduramamanız sinir bozucu. Bu, hangi nedenle olursa olsun, dizenizi aşamalar halinde biçimlendirmeniz gerektiğinde ve bu aşamaların sırasını biliyorsanız kullanışlıdır.
aaren

1
Muhtemelen bunu yapmak zorunda kalmadan kendi yolunuzu tasarlamalısınız, ama belki de bunu yapmak zorunda kalıyorsunuz.
aaren

2
Bunu bilmiyordum. Bir dizeyi mini şablon olarak "astarlamak" istediğim birkaç kullanım
durumum oldu

Bu, kodunuzun bir bölümünde bir dizenin bir bölümünü doldururken, ancak kodunuzun başka bir bölümünde daha sonra doldurulacak bir yer tutucu bırakırken çok kullanışlıdır.
Alex Petralia

99

Kısa, en okunaklı olan ve ayrıca kodlayıcının niyetini açıklayan partialişlevi kullanabilirsiniz functools:

from functools import partial

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

2
Sadece en kısa ve en okunabilir çözüm değil, aynı zamanda kodlayıcının niyetini de açıklıyor. Python3 sürümü: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 true, cevabın biraz sevgiye ihtiyacı var;)
ypercubeᵀᴹ

8
@ ypercubeᵀᴹ Pek çok insanın aradığı şeyin tam olarak bu olduğundan emin değilim. partial()kısmen biçimlendirilmiş dizeyle (yani "FOO {bar}") bazı işlemler yapmam gerekirse bana yardımcı olmayacak .
Delgan

1
Bu,% 100 kontrol etmediğiniz bir girdi üzerinde çalıştığınızda daha iyidir. Düşünün: "{foo} {{bar}}".format(foo="{bar}").format(bar="123")diğer örneklerden. Beklerdim "{bar} 123"ama çıktılar "123 123".
Benjamin Manns

50

Bu sınırlama .format()- kısmi ikame yapamama - beni rahatsız ediyor.

FormatterBuradaki birçok cevapta açıklandığı gibi özel bir sınıf yazmayı değerlendirdikten ve hatta lazy_format gibi üçüncü taraf paketleri kullanmayı düşündükten sonra , çok daha basit bir dahili çözüm keşfettim: Şablon dizeleri

Benzer işlevsellik sağlar, ancak aynı zamanda kısmi ikame eksiksiz safe_substitute()yöntem sağlar. Şablon dizelerinin bir $öneki olması gerekir (bu biraz tuhaf geliyor - ancak genel çözümün daha iyi olduğunu düşünüyorum).

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'

Buna göre bir kolaylık paketi oluşturdu:

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

Benzer şekilde, varsayılan dize biçimlendirmesini kullanan, Sven'in cevabına dayalı bir sarmalayıcı:

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

Bunun hızlı bir çözüm olarak uygun olup olmadığından emin değilim, ama ne dersin?

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

? :)


Ben de aynısını yaptım, keşke bunu yaparken uyarılar olup olmadığını bilseydim.
ramgo

11

Yöntemi Formattergeçersiz kılan kendinizinkini tanımlarsanız, get_valuetanımsız alan adlarını istediğiniz gibi eşlemek için bunu kullanabilirsiniz:

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

Örneğin, harita olabilir bariçin "{bar}"eğer barkwargs değildir.

Ancak, bu format(), dizenin format()yöntemini değil, Biçimlendirici nesnenizin yöntemini kullanmayı gerektirir .


Bir python> = 2.6 özelliği gibi görünüyor.
n611x007

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

Bunu deneyin.


Vay canına, tam da ihtiyacım olan şey! Açıklar mısın
Sergey Chizhik

1
{{ve }}biçimlendirme işaretlerinden kaçmanın bir yoludur, dolayısıyla format()ikame yapmaz ve sırasıyla {{ve }}ile değiştirir . {}
7yl4r

Bu çözümün sorunu, dublörün {{ }}yalnızca bir format için işe yaramasıdır, daha fazlasını uygulamanız gerekirse, daha fazlasını eklemeniz gerekir {}. ex. 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)ikinci biçim topic_iddeğeri sağlamadığından hata verecektir .
Franzi

7

Sayesinde Amber bireyin açıklama, ben bu geldi:

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

Bir python> = 2.6 özelliği gibi görünüyor.
n611x007

Kesinlikle bu çözümü kullanıyorum :) Teşekkürler!
astrojuanlu

2
Bunun, varsa dönüştürme ve biçim belirtimini kaybedeceğini unutmayın (ve aslında biçim belirtimini döndürülen değere uygular. Yani (şu {field!s: >4}olur{field}
Brendan Abel

3

Benim için bu yeterince iyiydi:

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

Bulduğum tüm çözümlerin daha gelişmiş özellik veya dönüştürme seçenekleriyle ilgili sorunları var gibi görünüyordu. @ SvenMarnach'ın FormatPlaceholder'ı harika bir şekilde zekice ama zorlamayla düzgün çalışmıyor (örneğin {a!s:>2s}) çünkü onun __str__yerine yöntemi çağırıyor (bu örnekte) __format__ve ek biçimlendirmeyi kaybediyorsunuz.

İşte bulduğum sonuç ve temel özelliklerinden bazıları:

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(yalnızca bir eşleme değil) benzer bir arayüz sağlar
  • daha karmaşık biçimlendirme seçeneklerini destekler:
    • zorlama {k!s} {!r}
    • yuvalama {k:>{size}}
    • getattr {k.foo}
    • GetItem {k[0]}
    • zorlama + biçimlendirme {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)

Bu yöntemin nasıl davranmasını istediğime dair bazı testler yazdıktan sonra çeşitli uygulamalarla ilgili sorunları keşfettim. Herhangi biri onları anlayışlı bulursa aşağıdadırlar.

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 koduna benzer ancak testleriniz için zorlamayı doğru bir şekilde ele alan bir cevap ekledim.
Tohiko

1

Önerim şu olacaktır (Python3.6 ile test edilmiştir):

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'

Güncelleme: Daha da zarif bir yol (alt sınıflandırma dictve aşırı yükleme __missing__(self, key)) burada gösterilmektedir: https://stackoverflow.com/a/17215533/333403


0

Tamamen doldurulana kadar dizeyi kullanmayacağınızı varsayarsak, şu sınıf gibi bir şey yapabilirsiniz:

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)

Misal:

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

Bunu başarmanın bir yolu daha var, yani değişkenleri kullanarak formatve %değiştirerek. Örneğin:

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

0

Benim için çok çirkin ama en basit çözüm şunu yapmaktır:

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

Bu şekilde tmpl, normal şablon olarak kullanmaya devam edebilir ve yalnızca gerektiğinde kısmi biçimlendirme gerçekleştirebilirsiniz. Mohan Raj'ınki gibi aşırı öldürücü bir çözüm kullanmak için bu sorunu çok önemsiz buluyorum.


0

En umut verici çözümleri burada ve orada test ettikten sonra , hiçbirinin aşağıdaki gereksinimleri gerçekten karşılamadığını fark ettim:

  1. str.format_map()şablon için tarafından tanınan sözdizimine kesinlikle uyun ;
  2. karmaşık biçimlendirmeyi koruyabilme, yani Biçim Mini Dili'ni tam olarak destekleyebilme

Bu yüzden, yukarıdaki gereksinimleri karşılayan kendi çözümümü yazdım. ( DÜZENLEME : şimdi @SvenMarnach'ın sürümü - bu yanıtta bildirildiği gibi - ihtiyacım olan köşe vakalarını ele alıyor gibi görünüyor).

Temel olarak, şablon dizesini ayrıştırdım, eşleşen iç içe geçmiş {.*?}grupları buldum (bir find_all()yardımcı işlev kullanarak ) ve biçimlendirilmiş dizeyi aşamalı olarak ve doğrudan kullanarak str.format_map()herhangi bir potansiyeli yakalarken oluşturdum 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

(Bu kod FlyingCircus'ta da mevcuttur - SORUMLULUK REDDİ: Ben onun ana yazarıyım .)


Bu kodun kullanımı şu şekilde olacaktır:

print(safe_format_map('{a} {b} {c}', dict(a=-A-)))
# -A- {b} {c}

Bunu en sevdiğim çözümle karşılaştıralım (kodunu burada ve orada nazikçe paylaşan @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))

İşte birkaç test:

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=[])

ve çalışmasını sağlayacak kod:

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

sonuçlanan:

    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}

Gördüğünüz gibi, güncellenmiş sürüm artık önceki sürümün başarısız olduğu köşe durumlarını iyi idare ediyor gibi görünüyor.


Zamanla, yakl. Birbirlerinin% 50'si, gerçek textformata (ve muhtemelen gerçek olana source) bağlı olarak, ancak safe_format_map()yaptığım testlerin çoğunda bir üstünlüğe sahip gibi görünüyor (elbette ne anlama gelirse gelsin):

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]}Bildiğim kadarıyla bunun geçerli bir biçim dizesi olmadığını unutmayın .
Sven Marnach

@SvenMarnach resmi belgeler açıkça söylemek field_name ::= arg_name ("." attribute_name | "[" element_index "]")*ve hem str.format()ve str.format_map()bunu anlamak. Bunun geçerli bir biçim dizesi olduğuna dair yeterli kanıt olduğunu söyleyebilirim.
norok2

str.format()Köşeli parantez içinde tamsayı olmayan bir indeks kullanımına bir örnek verebilir misiniz ? Yalnızca tamsayı dizinlerini çalıştırabilirim.
Sven Marnach

@SvenMarnach a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))size 'YAY!'
norok2

1
Ah, anlıyorum. Bunun a[b]Python kodundaki gibi yorumlanacağını varsayıyordum , ama aslında a["b"]Teşekkürler!
Sven Marnach

0

Eğer argüman geçmek bir sözlük paketten isterseniz format, bu ilgili soru olarak , aşağıdaki yöntemi kullanabilirsiniz.

Önce dizenin sbu sorudakiyle aynı olduğunu varsayalım :

s = '{foo} {bar}'

ve değerler aşağıdaki sözlükte verilmiştir:

replacements = {'foo': 'FOO'}

Açıkça bu işe yaramayacak:

s.format(**replacements)
#---------------------------------------------------------------------------
#KeyError                                  Traceback (most recent call last)
#<ipython-input-29-ef5e51de79bf> in <module>()
#----> 1 s.format(**replacements)
#
#KeyError: 'bar'

Bununla birlikte, ilk olabilir bir olsun setgelen adlandırılmış argümanlar tüms ve küme parantezleri sarılı kendisine argüman eşleştiren bir sözlük oluşturmak:

from string import Formatter
args = {x[1]:'{'+x[1]+'}' for x in Formatter().parse(s)}
print(args)
#{'foo': '{foo}', 'bar': '{bar}'}

Şimdi argseksik anahtarları doldurmak için sözlüğü kullanın replacements. Python 3.5+ için bunu tek bir ifadede yapabilirsiniz :

new_s = s.format(**{**args, **replacements}}
print(new_s)
#'FOO {bar}'

Python'un eski sürümleri için şunları arayabilirsiniz update:

args.update(replacements)
print(s.format(**args))
#'FOO {bar}'

0

@ Sven-marnach cevabını beğendim. Cevabım basitçe genişletilmiş bir versiyonu. Anahtar sözcük olmayan biçimlendirmeye izin verir ve fazladan anahtarları yok sayar. Aşağıda kullanım örnekleri verilmiştir (bir işlevin adı python 3.6 f-string biçimlendirmesine bir referanstır):

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

Ve işte kodum:

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

Çok fazla şablonlama yapıyorsanız ve Python'un yerleşik dizi şablonlama işlevselliğini yetersiz veya hantal buluyorsanız, Jinja2'ye bakın. .

Dokümanlardan:

Jinja, Django'nun şablonlarına göre modellenmiş, Python için modern ve tasarımcı dostu bir şablonlama dilidir.


0

@ Sam Bourne yorumunu okurken @ SvenMarnach'ın kodunu , {a!s:>2s}özel bir ayrıştırıcı yazmadan zorlama (benzeri ) ile düzgün çalışması için değiştirdim . Temel fikir dizelere dönüştürmek değil, eksik anahtarları zorlama etiketleriyle birleştirmektir.

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)

Bunun gibi (örneğin) kullanın

SafeFormatter().format("{a:<5} {b:<10}", a=10)

Aşağıdaki testler (@ norok2'deki testlerden esinlenerek) , iki durumda geleneksel format_mapve a safe_format_mapyukarıdaki sınıfa dayalı çıktıları kontrol eder : doğru anahtar sözcükler sağlamak veya bunlar olmadan.

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

Hangi çıktılar

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

Varsayılan argümanları alan bir fonksiyona sarabilirsiniz:

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

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

{Foo} 'yu boş bir dizeyle değiştiriyorsunuz. Soru, eksik alanları göz ardı etmekle değil, daha fazla son biçimlendirme için kısmi biçimlendirmeyle ilgilidir.
egvo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.