【问题标题】:Python tabstop-aware len() and padding functionsPython tabstop 感知 len() 和填充函数
【发布时间】:2010-12-17 07:06:39
【问题描述】:

Python 的 len() 和像 string.ljust() 这样的填充函数不支持制表位,即它们将 '\t' 视为任何其他单角字符,并且不会将 len 向上舍入到最接近的制表位倍数. 示例:

len('Bear\tnecessities\t')

是 17 而不是 24(即 4+(8-4)+11+(8-3) )

说我也想要一个函数pad_with_tabs(s) 这样

pad_with_tabs('Bear', 15) = 'Bear\t\t'

寻找这些的简单实现 - 首先是紧凑性和可读性,其次是效率。 这是一个基本但令人恼火的问题。 @gnibbler - 你能展示一个纯粹的 Pythonic 解决方案吗,即使它的效率降低了 20 倍?

当然,您可以使用 str.expandtabs(TABWIDTH) 来回转换,但这很笨重。 导入数学以获取 TABWIDTH * int( math.ceil(len(s)*1.0/TABWIDTH) ) 似乎也太过分了。

我没有比以下更优雅的方法了:

TABWIDTH = 8

def pad_with_tabs(s,maxlen):
  s_len = len(s)
  while s_len < maxlen:
    s += '\t'
    s_len += TABWIDTH - (s_len % TABWIDTH)
  return s

并且由于 Python 字符串是不可变的,除非我们想将函数修补到字符串模块中以将其添加为方法,否则我们还必须分配给函数的结果:

s = pad_with_tabs(s, ...)

尤其是我无法使用 list-comprehension 或 string.join(...) 获得干净的方法

''.join([s, '\t' * ntabs])

没有特殊情况,len(s) =maxlen。

谁能展示更好的 len() 和 pad_with_tabs() 函数?

【问题讨论】:

  • 不清楚你想要什么或者 str.expandtabs 如何不符合要求。一些示例输入和输出将有助于澄清。
  • 我在第一行说了我想要的:len() 和 string.ljust() 的实现,它们都是制表符感知的。 str.expandtabs() 不符合要求,因为它将制表符爆炸为空格。如果我们只想测量 len(),我们不希望这样。使用 len(string.expandtabs(s)) 生成一次性副本似乎很浪费
  • "使用 len(string.expandtabs(s)) 生成一次性副本似乎很浪费" 为什么?对我来说,这似乎简单而干净。您是否有任何特定的配置文件编号表明这是您的应用程序的瓶颈?
  • 如果你真的需要性能,就用C写函数
  • 伙计们 - 让我们忽略效率并追求紧凑可读的 Pythonic 代码? (即使效率降低了 20 倍)gnibbler 有一个很好的解决方案,还有其他条目吗?

标签: python function padding tabstop


【解决方案1】:
TABWIDTH=8
def my_len(s):
    return len(s.expandtabs(TABWIDTH))

def pad_with_tabs(s,maxlen):
    return s+"\t"*((maxlen-len(s)-1)/TABWIDTH+1)

为什么我使用expandtabs()
嗯,很快

$ python -m timeit '"Bear\tnecessities\t".expandtabs()'
1000000 loops, best of 3: 0.602 usec per loop
$ python -m timeit 'for c in "Bear\tnecessities\t":pass'
100000 loops, best of 3: 2.32 usec per loop
$ python -m timeit '[c for c in "Bear\tnecessities\t"]'
100000 loops, best of 3: 4.17 usec per loop
$ python -m timeit 'map(None,"Bear\tnecessities\t")'
100000 loops, best of 3: 2.25 usec per loop

任何迭代你的字符串的东西都会变慢,因为即使你在循环中什么都不做,迭代也比expandtabs慢约4倍。

$ python -m timeit '"Bear\tnecessities\t".split("\t")'
1000000 loops, best of 3: 0.868 usec per loop

即使只是在标签上拆分也需要更长的时间。您仍然需要遍历拆分并将每个项目填充到制表位

【讨论】:

【解决方案2】:

我相信 gnibbler 是最适合大多数实际情况的。但无论如何,这是一个简单的(不考虑 CR、LF 等)解决方案来计算字符串的长度而不创建扩展副本:

def tab_aware_len(s, tabstop=8):
    pos = -1
    extra_length = 0
    while True:
        pos = s.find('\t', pos+1)
        if pos<0:
            return len(s) + extra_length
        extra_length += tabstop - (pos+extra_length) % tabstop - 1

它可能对一些巨大的字符串甚至内存映射文件很有用。这里是填充功能有点优化:

def pad_with_tabs(s, max_len, tabstop=8):
    length = tab_aware_len(s, tabstop)
    if length<max_len:
        s += '\t' * ((max_len-1)//tabstop + 1 - length//tabstop)
    return s

【讨论】:

    【解决方案3】:

    TABWIDTH * int( math.ceil(len(s)*1.0/TABWIDTH) ) 确实是个大杀器;您可以更简单地获得相同的结果。对于积极的in,使用:

    def round_up_positive_int(i, n):
        return ((i + n - 1) // n) * n
    

    经过适当的翻译,此过程几乎适用于我曾经使用过的任何语言。

    那你就可以next_pos = round_up_positive_int(len(s), TABWIDTH)

    为了稍微增加代码的优雅性,而不是

    while(s_len < maxlen):
    

    使用这个:

    while s_len < maxlen:
    

    【讨论】:

      【解决方案4】:

      不幸的是,我无法“按原样”使用已接受的答案,所以这里稍微修改了版本,以防有人遇到同样的问题并通过搜索发现这篇文章:

      from decimal import Decimal, ROUND_HALF_UP
      TABWIDTH = 4
      
      def pad_with_tabs(src, max_len):
          return src + "\t" * int(
              Decimal((max_len - len(src.expandtabs(TABWIDTH))) / TABWIDTH + 1).quantize(0, ROUND_HALF_UP))
      
      
      def pad_fields(input):
          result = []
          longest = max(len(x) for x in input)
          for row in input:
              result.append(pad_with_tabs(row, longest))
          return result
      

      输出列表包含正确填充的行,其制表符计数已四舍五入,因此当原始答案中未添加制表符时,无论角点 0.5 情况如何,结果数据都将具有相同的缩进级别。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-01
        • 2019-06-11
        • 1970-01-01
        • 2010-10-28
        • 2012-06-10
        • 1970-01-01
        • 2016-02-18
        • 1970-01-01
        相关资源
        最近更新 更多