4

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) < TABWIDTH 的整数倍,或者 len(s)>=maxlen。

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

4

4 回答 4

8
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

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

于 2009-11-17T02:34:14.603 回答
1

我相信 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
于 2009-11-17T14:46:06.490 回答
0

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:
于 2009-11-17T14:37:37.297 回答
0

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

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 情况如何,结果数据都将具有相同的缩进级别。

于 2020-09-09T07:38:14.517 回答