【问题标题】:Slicing strings in str.format以 str.format 切片字符串
【发布时间】:2012-12-28 16:47:03
【问题描述】:

我想通过str.format实现以下目标:

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

我能做到的唯一方法是:

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

现在,这是一个示例,我真正拥有的是一个又长又乱的字符串,所以我想将切片放在 {} 中。我研究过the docs,但我无法弄清楚正确的语法。我的问题是:是否可以在替换字段中分割字符串?

【问题讨论】:

    标签: python string string-formatting


    【解决方案1】:

    不,您不能对替换字段内的字符串应用切片。

    您需要参考Format Specification Mini-Language;它定义了可能的。这种迷你语言定义了如何格式化引用的值(替换字段语法中 : 之后的部分)。

    【讨论】:

    • 请注意,您可以获取单个字符 ("{foo[0]}".format(foo="bar")),但这不是真正的 Python 切片。
    • @katrielalex:那是因为字段名语法支持项目访问;它适用于字典和列表,真的。
    • 您可以以类似于 % 格式的方式指定最小字符数 - {0:4d} 会将第一个 int 项目 0 填充为 4 位。但是给它 5 位数字,它就不会切片或截断。
    • 是的,这就是我的观点 - OP 并不是完全疯狂地期望切片,只是不幸的是它尚未实现。 =)
    • @katrielalex:我怀疑它会被实施;目标是通过项和属性访问(也称为遍历)找到更复杂结构中的对象。切片完全是一种不同的操作。
    【解决方案2】:

    你可以做这样的事情。

    注意
    这是一个粗略的例子,不应该被认为是完整的和经过测试的。但我认为它向您展示了一种开始到达您想去的地方的方法。

    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:] 这样的切片,但我认为这也很容易实现。切片索引超出范围等也没有错误检查。

    【讨论】:

      【解决方案3】:

      直接回答您的问题:不,内置 str 格式不支持切片。虽然,如果 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。 attr 和项目访问在 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"
      

      【讨论】:

        【解决方案4】:

        您可以使用运行时评估的“f”字符串。 Python f-strings 支持切片,并且不使用格式化程序之类的“迷你语言”。在 f 字符串的每个花括号中都可以使用 python 表达式的全部功能。不幸的是,没有 string.feval() 函数...... imo 应该有(语言不应该有没有提供给用户的魔法能力)。

        你也不能给字符串类型加一个,因为内置的python类型不能修改/扩展。

        有关运行时评估 f 字符串的示例,请参阅 https://stackoverflow.com/a/49884004/627042

        【讨论】:

          【解决方案5】:

          这是一个很好的解决方案,很好地解决了我的切片问题。但是,我也想做价值消除。例如,我可能想在 10 个字符的字段中填充的“AveryLongStringValue”可能会被截断为“...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'。对于其他可能感兴趣的人。非常感谢。

          【讨论】:

            猜你喜欢
            • 2020-11-27
            • 1970-01-01
            • 2014-07-21
            • 2012-02-15
            • 1970-01-01
            • 1970-01-01
            • 2017-10-16
            • 2013-10-29
            相关资源
            最近更新 更多