在str.format中对字符串进行切片

26

我希望使用 str.format 实现以下功能:

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

我能做到的唯一方式是:

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

现在,这只是一个示例,我真正拥有的是一个又长又乱的字符串,因此我想在{}中放置切片。我已经研究了文档,但我无法弄清正确的语法。我的问题是:是否可能在替换字段中切割字符串?

6个回答

19

不可以在替换字段内对字符串进行切片。

您需要参考格式规范微语言;该微语言定义了可行的操作。该微语言定义了如何格式化引用值(替换字段语法中冒号后面的部分)。


5
请注意,您可以获取单个字符("{foo[0]}".format(foo="bar")),但这并不是真正的 Python 切片操作。 - Katriel
@katrielalex:这是因为字段名称语法支持项访问;它实际上是为字典和列表而设计的。 - Martijn Pieters
您可以通过类似于%格式的方式指定最小字符数 - {0:4d} 将会在第一个整数项前面填充 0,使其达到 4 位数。但是如果给它 5 位数,它将不会被切片或截断。 - Danny Staple
4
是的,那就是我的观点 - OP 并不完全疯狂地期望切片操作,只是不幸的是它还没有被实现。=) - Katriel
1
@katrielalex:我怀疑它不会被实现,因为目标是通过项目和属性访问,也称为遍历,在更复杂的结构中查找对象。切片是一种完全不同的操作。 - Martijn Pieters

8
你可以这样做。
注意: 这只是一个简单的范例,不能被视为完整且经过测试的。但我认为它可以帮助你开始实现你的需求。
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")

输出

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

注意2
不支持像[:5][6:]这样的切片,但我认为这也很容易实现。此外,没有对切片索引超出范围等情况进行错误检查。


2
你可以使用运行时评估的"f"字符串。Python f-strings支持切片,不像格式化程序那样使用“mini-language”。在f-string的每个花括号内都可以使用Python表达式的全部功能。不幸的是,没有string.feval()函数...imo应该有(语言不应该具有用户未提供的魔法能力)。
你也不能将数字1添加到字符串类型中,因为内置的Python类型不能被修改或扩展。
请参见https://dev59.com/5lgQ5IYBdhLWcg3wnFLJ#49884004以了解一个运行时评估的f-string的示例。

2
直接回答您的问题:不,内置的字符串格式化不支持切片。虽然,如果运行时评估的f-strings不符合您的需求,有一个解决方法。
解决方法:
之前对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

解释:

说明

get_valueget_field中被调用,仅用于访问vformat()中的args和kwargs。属性和项的访问是在get_field中完成的。因此,在super().get_field返回所需的obj之后应执行切片访问。

话虽如此,重载get_value会导致在对象遍历后无法对其进行切片处理。您可以在此示例中看到错误:

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

1
这是一个不错的解决方案,很好地解决了我的切片问题。但是,我也想进行值省略。例如,我想将“AVeryLongStringValue”塞入一个长度为10的字段中,可能会被截断为“... ngValue”。因此,我扩展了您的示例,支持同时进行切片、省略和正常格式化。以下是我得出的代码。
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

作为示例,可以将所有三个格式规范组合在一起,以剪切值的第一个和最后一个字符,将切片居中省略到10个字符值,最后将其右对齐在12个字符字段中,如下所示:
sefmtr = SliceElideFormatter()
data = { 'CNT':'centeredtextvalue' }
fmt = '{CNT|1,-1$10^**:>12}'
print '%r' % sefmtr.format(fmt, *(), **data)

输出结果:' ente**valu'。对于任何其他可能感兴趣的人。非常感谢。


-1

我尝试在Python 3.9中实现它,效果很好

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

输出

slicing is possible now

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接