简体   繁体   English

以 str.format 切片字符串

[英]Slicing strings in str.format

I want to achieve the following with str.format :我想用str.format实现以下目标:

x,y = 1234,5678
print str(x)[2:] + str(y)[:2]

The only way I was able to do it was:我能做到的唯一方法是:

print '{0}{1}'.format(str(x)[2:],str(y)[:2])

Now, this an example and what I really have is a long and messy string, and so I want to put slicing inside the {} .现在,这是一个例子,我真正拥有的是一个又长又乱的字符串,所以我想把切片放在{}里面。 I've studied the docs , but I can't figure out the correct syntax.我研究了文档,但我无法弄清楚正确的语法。 My question is: is it possible to slice strings inside a replacement field?我的问题是:是否可以在替换字段中分割字符串?

No, you can't apply slicing to strings inside a the replacement field.不,您不能对替换字段内的字符串应用切片。

You'll need to refer to the Format Specification Mini-Language ;您需要参考Format Specification Mini-Language it defines what is possible.它定义了什么可能的。 This mini language defines how you format the referenced value (the part after the : in the replacement field syntax).这种迷你语言定义了如何格式化引用的值(替换字段语法中:之后的部分)。

You could do something like this.你可以做这样的事情。

NOTE笔记
This is a rough example and should not be considered complete and tested.这是一个粗略的例子,不应该被认为是完整的和经过测试的。 But I think it shows you a way to start getting where you want to be.但我认为它向你展示了一种开始到达你想去的地方的方法。

import string

class SliceFormatter(string.Formatter):

    def get_value(self, key, args, kwds):
        if '|' in key:
            try:
                key, indexes = key.split('|')
                indexes = map(int, indexes.split(','))
                if key.isdigit():
                    return args[int(key)][slice(*indexes)]
                return kwds[key][slice(*indexes)]
            except KeyError:
                return kwds.get(key, 'Missing')
        return super(SliceFormatter, self).get_value(key, args, kwds)


phrase = "Hello {name|0,5}, nice to meet you.  I am {name|6,9}.  That is {0|0,4}."
fmt = SliceFormatter()
print fmt.format(phrase, "JeffJeffJeff", name="Larry Bob")

OUTPUT输出

Hello Larry, nice to meet you.  I am Bob.  That is Jeff.

NOTE 2笔记2
There is no support for slicing like [:5] or [6:] , but I think that would be easy enough to implement as well.不支持像[:5][6:]这样的切片,但我认为这也很容易实现。 Also there is no error checking for slice indexes out of range, etc.此外,对于超出范围的切片索引等也没有错误检查。

You can use a run-time evaluated "f" string.您可以使用运行时评估的“f”字符串。 Python f-strings support slicing and don't use a "mini-language" like the formatter. Python f-strings 支持切片,并且不使用格式化程序之类的“迷你语言”。 The full power of a python expression is available within each curly-brace of an f-string.在 f 字符串的每个花括号中都可以使用 python 表达式的全部功能。 Unfortunately there is no string.feval() function ... imo there should be (languages should not have magic abilities that are not provided to the user).不幸的是,没有 string.feval() 函数...... imo 应该有(语言不应该有没有提供给用户的魔法能力)。

You also can't add one to the string type, because the built-in python types cannot be modified/expanded.您也不能在字符串类型中添加一个,因为无法修改/扩展内置的 python 类型。

See https://stackoverflow.com/a/49884004/627042 for an example of a run-time evaluates f-string.有关运行时评估 f 字符串的示例,请参阅https://stackoverflow.com/a/49884004/627042

Straight answering your question: No, slicing is not supported by builtin str formatting.直接回答您的问题:不,内置 str 格式不支持切片。 Although, there is a workaround in case f-strings (runtime evaluated) don't fit your needs.虽然,如果 f-strings(运行时评估)不符合您的需求,有一种解决方法。

Workaround解决方法

The previous answers to extend string.Formatter are not completely right, since overloading get_value is not the correct way to add the slicing mechanism to string.Formatter .之前扩展string.Formatter的答案并不完全正确,因为重载 get_value 不是将切片机制添加到string.Formatter的正确方法。

import string


def transform_to_slice_index(val: str):
    if val == "_":
        return None
    else:
        return int(val)


class SliceFormatter(string.Formatter):

    def get_field(self, field_name, args, kwargs):
        slice_operator = None
        if type(field_name) == str and '|' in field_name:
            field_name, slice_indexes = field_name.split('|')
            slice_indexes = map(transform_to_slice_index,
                                slice_indexes.split(','))
            slice_operator = slice(*slice_indexes)

        obj, first = super().get_field(field_name, args, kwargs)
        if slice_operator is not None:
            obj = obj[slice_operator]

        return obj, first

Explanation解释

get_value is called inside get_field and it is used ONLY to access the args and kwargs from vformat() . get_valueget_field内部调用,它仅用于从vformat()访问 args 和 kwargs。 attr and item accessing is done in get_field. attr 和项目访问在 get_field 中完成。 Thus, the slice access should be done after super().get_field returned the desired obj.因此,切片访问应该在 super().get_field 返回所需的 obj 之后完成。

With this said, overloading get_value gives you the problem that the formatter would not work for slicing after the object is traversed.话虽如此,重载 get_value 会给您带来一个问题,即在遍历对象后格式化程序将无法用于切片。 You can see the error in this example:您可以在此示例中看到错误:

WrongSliceFormatter().format("{foo.bar[0]|1,3}", foo=foo)
>> ValueError: "Only '.' or '[' may follow ']' in format field specifier"

This is a nice solution and solved my slicing problem quite nicely.这是一个很好的解决方案,很好地解决了我的切片问题。 However, I also wanted to do value eliding as well.然而,我也想做价值消除。 For example 'AVeryLongStringValue' that I might want to stuff in a 10 character field, might be truncated to '...ngValue'.例如,我可能想要填充 10 个字符字段的“AveryLongStringValue”可能会被截断为“...ngValue”。 So I extended your example to support slicing, eliding, and normal formatting all in one.因此,我扩展了您的示例以支持切片、删除和正常格式合二为一。 This is what I came up with.这就是我想出的。

class SliceElideFormatter(string.Formatter):
    """An extended string formatter that provides key specifiers that allow
    string values to be sliced and elided if they exceed a length limit.  The
    additional formats are optional and can be combined with normal python
    formatting.  So the whole syntax looks like:
    key[|slice-options][$elide-options[:normal-options]
    Where slice options consist of '|' character to begin a slice request,
    followed by slice indexes separated by commas.  Thus {FOO|5,} requests
    everything after the 5th element.
      The elide consist of '$' character followed by an inter max field value,
    followed by '<', '^', or '>' for pre, centered, or post eliding, followed
    by the eliding string.  Thus {FOO$10<-} would display the last 9 chanacters
    of a string longer then 10 characters with '-' prefix.
      Slicing and eliding can be combined.  For example given a dict of
    {'FOO': 'centeredtextvalue', and a format string of 
    '{FOO|1,-1$11^%2E%2E%2E}' would yield 'ente...valu'.  The slice spec removes
    the first and last characrers, and the elide spec center elides the
    remaining value with '...'.  The '...' value must be encoded in URL format
    since . is an existing special format character.
    """

    def get_value(self, key, args, kwds):
        """Called by string.Formatter for each format key found in the format
        string.  The key is checked for the presence of a slice or elide intro-
        ducer character.  If one or both a found the slice and/or elide spec
        is extracted, parsed and processed on value of found with the remaining
        key string.
        Arguments:
          key, A format key string possibly containing slice or elide specs
          args, Format values list tuple
          kwds, Format values key word dictrionary
        """
        sspec = espec = None
        if '|' in key:
            key, sspec = key.split('|')
            if '$' in sspec:
                sspec, espec = sspec.split('$')
        elif '$' in key:
            key, espec = key.split('$')
        value = args[int(key)] if key.isdigit() else kwds[key]
        if sspec:
            sindices = [int(sdx) if sdx else None
                        for sdx in sspec.split(',')]
            value = value[slice(*sindices)]
        if espec:
            espec = urllib.unquote(espec)
            if '<' in espec:
                value = self._prefix_elide_value(espec, value)
            elif '>' in espec:
                value = self._postfix_elide_value(espec, value)
            elif '^' in espec:
                value = self._center_elide_value(espec, value)
            else:
                raise ValueError('invalid eliding option %r' % elidespec)
        if sspec or espec:
            return value

        return super(SliceElideFormatter,self).get_value(key, args, kwds)

    def _center_elide_value(self, elidespec, value):
        """Return center elide value if it exceeds the elide length.
        Arguments:
          elidespec, The elide spec field extracted from key
          value, Value obtained from remaing key to maybe be elided
        """
        elidelen, elidetxt = elidespec.split('^')
        elen, vlen = int(elidelen), len(value)
        if vlen > elen:
            tlen = len(elidetxt)
            return value[:(elen-tlen)//2] + elidetxt + value[-(elen-tlen)//2:]
        return value

    def _postfix_elide_value(self, elidespec, value):
        """Return postfix elided value if it exceeds the elide length.
        Arguments:
          elidespec, The elide spec field extracted from key
          value, Value obtained from remaing key to maybe be elided
        """
        elidelen, elidetxt = elidespec.split('>')
        elen, vlen  = int(elidelen), len(value)
        if vlen > elen:
            tlen = len(elidetxt)
            return value[:(elen-tlen)] + elidetxt
        return value

    def _prefix_elide_value(self, elidespec, value):
        """Return prefix elided value if it exceeds the elide length.
        Arguments:
          elidespec, The elide spec field extracted from key
          value, Value obtained from remaing key to maybe be elided
        """
        elidelen, elidetxt = elidespec.split('<')
        elen, vlen  = int(elidelen), len(value)
        if vlen > elen:
            tlen = len(elidetxt)
            return elidetxt + value[-(elen-tlen):]
        return value

As an example all three format specs can be combined to clip the values first and last characters, center elide the slice to a 10 char value, and finally right justify it in a 12 char field as follows:例如,可以组合所有三种格式规范以剪切值的第一个和最后一个字符,将切片居中删除为 10 字符值,最后在 12 字符字段中右对齐,如下所示:

sefmtr = SliceElideFormatter()
data = { 'CNT':'centeredtextvalue' }
fmt = '{CNT|1,-1$10^**:>12}'
print '%r' % sefmtr.format(fmt, *(), **data)

Outputs: ' ente**valu'.输出:'ente**value'。 For anyone else that may be interested.对于其他可能感兴趣的人。 Thanks much.非常感谢。

I tried doing it in python 3.9 and it is working well我试过在 python 3.9 中做它,它运行良好

 x="nowpossible"
print(" slicing is possible {}".format(x[0:2]))

output输出

slicing is possible now

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM