আংশিক স্ট্রিং বিন্যাস


128

স্ট্রিং টেম্পলেট 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}
ম্যাক্সনাও

আমি বুঝতে পেরেছি যে "সর্বাধিক প্রাথমিক বাস্তবায়ন কেবলমাত্র বেসিক কেসগুলির জন্য সঠিকভাবে কাজ করে" তবে এটিকে প্রসারিত ফর্মটি মুছতে না পেরে এমনকি এটি প্রসারিত করার কোনও উপায় আছে কি?
তাধগ ম্যাকডোনাল্ড-জেনসেন

5
@ টেডজিএমসিডোনাল্ড-জেনসেন: হ্যাঁ, একটি উপায় আছে। স্ট্রিংটি ফিরে আসার পরিবর্তে, বিন্যাসের বৈশিষ্ট্য সহ মূল স্থানধারককে ফেরত দেওয়ার জন্য __missing__()কাস্টম শ্রেণীর ওভাররাইডের একটি উদাহরণ __format__()ফিরে আসুন। ধারণার প্রমাণ: ideone.com/xykV7R
সেভেন Marnach

@ স্পেনমার্নাচ আপনার ধারণার প্রমাণটি কেন আপনার উত্তরের শরীরে নেই? এটা কিছুটা অধরা। এটির প্রচার থেকে বিরত থাকার জন্য কি কোনও পরিচিত ক্যাভেট রয়েছে?
নরোক 2

1
@ নরোক 2 এটি একটি মন্তব্যে জিজ্ঞাসা করা একটি প্রশ্নের উত্তর, সুতরাং আমি একটি মন্তব্যে উত্তরটি রেখেছি। মূল প্রশ্নটি আসলে সেই প্রয়োজনটি অন্তর্ভুক্ত করেনি এবং আমি এখনও মনে করি যে একটি স্ট্রিংকে আংশিক বিন্যাস করার চেষ্টা করা কিছুটা অদ্ভুত।
সোভেন মারনাচ

128

আপনি যদি জানেন যে কোন ক্রমে আপনি জিনিসগুলির বিন্যাস করছেন:

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

এটি এর মতো ব্যবহার করুন:

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

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

আপনি নির্দিষ্ট করতে পারবেন না fooএবং barএকই সাথে - আপনাকে এটি ক্রমিকভাবে করতে হবে।


এর মূল কথা কি? যদি আমি foo এবং বার উভয়ই নির্দিষ্ট করে থাকি: s.format(foo='FOO',bar='BAR')তবে আমি এখনও পেয়েছি 'FOO {bar}', যাই হোক না কেন। আপনি এটা পরিষ্কার করতে পারেন?
n611x007

10
যে আপনি উভয়ই একবারে পূরণ করতে পারবেন না তা বিরক্তিকর। এটি কার্যকর যখন কোনও কারণেই হোক আপনার স্ট্রিংগুলিকে পর্যায়গুলিতে ফর্ম্যাট করতে হবে এবং আপনি এই পর্যায়ের ক্রমটি জানেন।
aaren

1
আপনার সম্ভবত এটি করার বাইরে উপায় তৈরি করা উচিত তবে এটি আপনাকে বাধ্য হতে পারে it
aaren

2
এই সম্পর্কে জানতাম না। আমার বেশ কয়েকটি ব্যবহারের কেস রয়েছে যেখানে আমি মিনি টেম্পলেট হিসাবে একটি স্ট্রিং "প্রাইম" করতে চেয়েছিলাম
এপ্রিল

আপনার কোডের এক অংশে একটি স্ট্রিংয়ের কিছু অংশ পপুলেটিং করার সময় এটি দরকারী তবে কোনও স্থানধারককে পরে আপনার কোডের অন্য অংশে পপুলেট করা যায়।
অ্যালেক্স পেট্রালিয়া

98

আপনি সেই partialফাংশনটি ব্যবহার করতে পারেন functoolsযা থেকে সংক্ষিপ্ত, সর্বাধিক পঠনযোগ্য এবং কোডারের উদ্দেশ্য বর্ণনা করে:

from functools import partial

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

2
শুধুমাত্র সংক্ষিপ্ত এবং সর্বাধিক পঠনযোগ্য সমাধানই নয়, কোডারের উদ্দেশ্য সম্পর্কেও বর্ণনা করে। পাইথন 3 সংস্করণ: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
পল ব্রাউন

@ পলব্রাউন সত্য, উত্তরের কিছুটা ভালবাসা দরকার;)
ypercubeᵀᴹ

8
@ ইয়পারকিউবᵀᴹ ভাল, আমি নিশ্চিত নই যে বেশিরভাগ লোকেরা এটিই খুঁজছেন। partial()আংশিক বিন্যাসিত স্ট্রিং (যেটি "FOO {bar}") দিয়ে কিছু প্রসেসিংয়ের প্রয়োজন হলে আমাকে সাহায্য করবে না ।
দেলগান

1
আপনি যদি 100% নিয়ন্ত্রণ না করেন এমন ইনপুটটিতে কাজ করছেন তখন ক্ষেত্রে এটির জন্য ভাল। কল্পনা করুন: "{foo} {{bar}}".format(foo="{bar}").format(bar="123")অন্যান্য উদাহরণ থেকে। আমি আশা করি "{bar} 123"তবে তারা আউটপুট দেয় "123 123"
বেনিয়ামিন মানস

50

.format()আংশিক প্রতিস্থাপনের অক্ষমতা - এর এই সীমাবদ্ধতা আমাকে হতাশ করে চলেছে।

Formatterএখানে অনেক উত্তরে বর্ণিত কাস্টম ক্লাস রচনা মূল্যায়ন করার পরেও এবং তৃতীয় পক্ষের প্যাকেজ যেমন অলস_ ফর্ম্যাট ব্যবহার করার বিষয়টি বিবেচনা করার পরেও আমি অনেক সহজ ইনবিল্ট সমাধান আবিষ্কার করেছি: টেম্পলেট স্ট্রিং

এটি অনুরূপ কার্যকারিতা সরবরাহ করে তবে আংশিক প্রতিস্থাপনের সম্পূর্ণ 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

একইভাবে সোভেনের উত্তরের উপর ভিত্তি করে একটি মোড়ক যা ডিফল্ট স্ট্রিং বিন্যাস ব্যবহার করে:

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

? :)


আমি সম্পূর্ণরূপে এটিই করলাম, যদি আমি জানতাম যে এটি করার ক্ষেত্রে সতর্কতা ছিল কিনা।
রামগো

11

আপনি যদি নিজের Formatterনিজস্বটি নির্দিষ্ট করে থাকেন যা get_valueপদ্ধতিটি ওভাররাইড করে , আপনি যা চান তা অপরিশোধিত ক্ষেত্রের নামগুলি ম্যাপ করার জন্য এটি ব্যবহার করতে পারেন:

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

উদাহরণস্বরূপ, আপনি কোয়ার্গসে না থাকলে মানচিত্র barকরতে "{bar}"পারেন bar

তবে এটির format()জন্য আপনার ফরম্যাটার অবজেক্টের পদ্ধতিটি ব্যবহার করা দরকার , স্ট্রিংয়ের format()পদ্ধতিটি নয়।


অজগর> = 2.6 বৈশিষ্ট্যের মতো মনে হচ্ছে।
n611x007

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

এটি চেষ্টা করে দেখুন।


বাহ, ঠিক আমার যা দরকার! আপনি এটা ব্যাখ্যা করতে হবে?
সার্জি চিজিক

1
{{এবং }}বিন্যাস চিহ্ন পলায়নের একটি উপায়, তাই format()প্রতিকল্পন এবং প্রতিস্থাপিত সঞ্চালন নেই {{এবং }}সঙ্গে {এবং }যথাক্রমে।
7yl4r

এই সমাধানটির বিষয়টি হ'ল ডাবলটি {{ }}কেবলমাত্র একটি বিন্যাসের জন্য কাজ করে, যদি আপনাকে আরও প্রয়োগ করতে হয় তবে আপনাকে আরও যুক্ত করতে হবে {}। প্রাক্তন। 'fd:{uid}:{{topic_id}}'.format(uid=123).format(a=1)দ্বিতীয় ফর্ম্যাটটি topic_idমান সরবরাহ না করায় ত্রুটি ফিরে আসবে ।
ফ্রানজি

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

অজগর> = 2.6 বৈশিষ্ট্যের মতো মনে হচ্ছে।
n611x007

আমি অবশ্যই এই সমাধানটি ব্যবহার করছি :) ধন্যবাদ!
অ্যাস্ট্রজুয়ানলু

2
সচেতন থাকুন যে এটি বিদ্যমান থাকলে রূপান্তর এবং ফর্ম্যাট স্পষ্টটি হারাবে (এবং এটি আসলে প্রত্যাবর্তিত মানটির জন্য ফর্ম্যাট স্পিকটি প্রয়োগ করে ie যেমন ( {field!s: >4}হয়ে যায়{field}
ব্রেন্ডন আবেল

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

আমি যে সকল সমাধান পেয়েছি তার মধ্যে আরও উন্নত স্পেস বা রূপান্তর বিকল্পের সমস্যা রয়েছে বলে মনে হয়েছে। @ সোভেনমার্নচের ফর্ম্যাটপ্লেসোল্ডার আশ্চর্যজনকভাবে চালাক তবে এটি জবরদস্তি দিয়ে সঠিকভাবে কাজ করে না (উদাঃ{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)

আমি একটি উত্তর যুক্ত করেছি যা @ স্বেয়েনমার্নাক কোডের অনুরূপ তবে এটি যা আপনার পরীক্ষার জন্য জোর করে সঠিকভাবে পরিচালনা করে।
তোহিকো 16

1

আমার পরামর্শটি নিম্নলিখিত হবে (পাইথন 3.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'

আপডেট: একটি আরও মার্জিত উপায় (subclassing 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নিয়মিত টেম্পলেট হিসাবে ব্যবহার করতে পারেন এবং যখন প্রয়োজন হয় তখনই আংশিক ফর্ম্যাটিং সম্পাদন করতে পারেন । মোহন রাজের মতো ওভারকিলিং সমাধান ব্যবহার করতে আমি এই সমস্যাটিকে তুচ্ছ মনে করি।


0

এখানে এবং সেখান থেকে সর্বাধিক প্রতিশ্রুতিবদ্ধ সমাধানগুলি পরীক্ষা করার পরে , আমি বুঝতে পেরেছিলাম যে এগুলির কোনওটিই নিম্নলিখিত প্রয়োজনীয়তাগুলি সত্যিই মেটেনি:

  1. দ্বারা স্বীকৃত বাক্য গঠন কঠোরভাবে মেনে চলুন str.format_map()টেমপ্লেটের জন্য চলুন;
  2. জটিল ফর্ম্যাটিং ধরে রাখতে সক্ষম হওয়া, অর্থাত্ মিনি-ল্যাঙ্গুয়েজে সম্পূর্ণরূপে সমর্থন

সুতরাং, আমি আমার নিজস্ব সমাধান লিখেছি, যা উপরের প্রয়োজনীয়তাগুলি পূরণ করে। ( সম্পাদনা করুন) : এখন @ স্বেভেনমার্নাচের সংস্করণ - এই উত্তরে উল্লিখিত হিসাবে - আমার প্রয়োজনীয় কোণার কেসগুলি হ্যান্ডেল করে মনে হচ্ছে)।

মূলত, আমি টেম্পলেট স্ট্রিংটি পার্সিং করে, নেস্টেড {.*?}গ্রুপগুলির সাথে মিল খুঁজে পেয়েছি (কোনও সহায়তা 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

(এই কোডটি ফ্লাইংক্রিসাসেও পাওয়া যায় - অস্বীকৃতি: আমি এর মূল লেখক))


এই কোডের ব্যবহার হ'ল:

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

আসুন এটি আমার প্রিয় সমাধানটির সাথে তুলনা করুন (@ স্যাভেনমার্নচ যিনি দয়া করে এখানে এবং সেখানে তাঁর কোডটি ভাগ করে দিয়েছেন ) দ্বারা:

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}

যেমন আপনি দেখতে পাচ্ছেন, আপডেট করা সংস্করণটি এখন কোণার কেসগুলি হ্যান্ডেল করে দেখায় যেখানে আগের সংস্করণটি ব্যর্থ হত।


সময় অনুযায়ী, তারা প্রায় মধ্যে। একে অপরের 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]}আমি জানি যতক্ষণ না এটি বৈধ বিন্যাসের স্ট্রিং নয় Note
সোভেন মারনাচ

@SvenMarnach সরকারী দস্তাবেজ স্পষ্টভাবে বলতে field_name ::= arg_name ("." attribute_name | "[" element_index "]")*এবং উভয় str.format()এবং str.format_map()এটা বুঝতে। আমি বলব এটির বৈধ বিন্যাসের স্ট্রিং হওয়ার যথেষ্ট প্রমাণ রয়েছে।
norok2

str.format()বর্গাকার বন্ধনীগুলিতে একটি অ-পূর্ণসংখ্যার সূচক সহ ব্যবহার করার উদাহরণ দিতে পারেন ? আমি কেবল পূর্ণসংখ্যার সূচিগুলি কাজ করতে পারি।
সোভেন মারনাচ

@ স্পেনমার্নাচ a = dict(b='YAY!'); '{a[b]}'.format_map(dict(a=a))আপনাকে Y ' ইয়ে !'
নরোক 2

1
আহ আমি দেখি. আমি ধরেছিলাম যে a[b]এটি পাইথন কোডের মতো ব্যাখ্যা করা হয়েছে তবে এটি আসলে a["b"]ধন্যবাদ!
সোভেন মারনাচ

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'

তবে, আপনি প্রথমে নাম দেওয়া সমস্ত আর্গুমেন্টের একটি পেতেsets পারেন এবং একটি অভিধান তৈরি করতে পারেন যা নিজের পক্ষে কুঁকড়ানো ধনুর্বন্ধনীতে আবৃত যুক্তিকে মানচিত্র করে:

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

argsহারিয়ে যাওয়া কীগুলি পূরণ করতে এখন অভিধানটি ব্যবহার করুন replacements। অজগর 3.5+ এর জন্য, আপনি এটি একক প্রকাশে করতে পারেন :

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

পাইথনের পুরানো সংস্করণগুলির জন্য, আপনি কল করতে পারেন update:

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

0

আমি @ সোভেন-মার্নচ উত্তরটি পছন্দ করি। আমার উত্তরটি কেবল এটির একটি বর্ধিত সংস্করণ। এটি অ-কীওয়ার্ড বিন্যাসকরণের অনুমতি দেয় এবং অতিরিক্ত কীগুলি উপেক্ষা করে। এখানে ব্যবহারের উদাহরণ রয়েছে (একটি ফাংশনের নাম পাইথন 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

যদি আপনি প্রচুর টেম্প্লেটিং করছেন এবং পাইথনের তৈরি স্ট্রিং টেম্প্লেটিং কার্যকারিতাটি অপর্যাপ্ত বা আটকানোর জন্য খুঁজে পেয়েছেন তবে জিনজা 2 দেখুন

ডক্স থেকে:

জিনজা পাইথনের আধুনিক ও ডিজাইনার-বান্ধব টেম্প্লেটিং ভাষা, যা জ্যাঙ্গোর টেমপ্লেটগুলির পরে মডেল।


0

পড়া @Sam বোর্ন মন্তব্য, আমি @ SvenMarnach এর পরিবর্তিত কোড কাজ সঠিকভাবে বলপ্রয়োগ (যেমন সঙ্গে {a!s:>2s}) একটি কাস্টম পার্সার লেখা ছাড়া। মূল ধারণাটি স্ট্রিংগুলিতে রূপান্তর নয় বরং জবরদস্তি ট্যাগগুলির সাহায্যে নিখোঁজ কীগুলি সংযুক্ত করে তোলা।

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। প্রতিস্থাপন করছেন। প্রশ্নটি অনুপস্থিত ক্ষেত্রগুলি উপেক্ষা না করে, আরও চূড়ান্ত বিন্যাসের জন্য আংশিক ফর্ম্যাটিং সম্পর্কে।
যেমন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.