使用 textwrap.wrap 和字节数
Using textwrap.wrap with bytes count
如何使用 textwrap
模块在一行达到一定字节数之前进行拆分(不拆分多字节字符)?
我想要这样的东西:
>>> textwrap.wrap('☺ ☺☺ ☺☺ ☺ ☺ ☺☺ ☺☺', bytewidth=10)
☺ ☺☺
☺☺ ☺
☺ ☺☺
☺☺
结果取决于使用的编码,因为每个字节数
字符是编码的函数,在许多编码中,
性格也是如此。我假设我们使用的是 UTF-8,其中 '☺'
是
编码为 e298ba
并且是三个字节长;给定的例子是
与该假设一致。
textwrap
中的所有内容都适用于角色;它什么都不知道
关于编码。解决此问题的一种方法是将输入字符串转换为
另一种格式,每个字符变成一串字符
其长度与字节长度成正比。我会用三个
字符:两个十六进制字节,加上一个控制换行符。
因此:
'a' -> '61x' non-breaking
' ' -> '20 ' breaking
'☺' -> 'e2x98xbax' non-breaking
为简单起见,我假设我们只在空格处打断,而不是制表符或任何
其他角色。
import textwrap
def wrapbytes(s, bytewidth, encoding='utf-8', show_work=False):
byts = s.encode(encoding)
encoded = ''.join('{:02x}{}'.format(b, ' ' if b in b' ' else 'x')
for b in byts)
if show_work:
print('encoded = {}\n'.format(encoded))
ewidth = bytewidth * 3 + 2
elist = textwrap.wrap(encoded, width=ewidth)
if show_work:
print('elist = {}\n'.format(elist))
# Remove trailing encoded spaces.
elist = [s[:-2] if s[-2:] == '20' else s for s in elist]
if show_work:
print('elist = {}\n'.format(elist))
# Decode. Method 1: inefficient and lengthy, but readable.
bl1 = []
for s in elist:
bstr = "b'"
for i in range(0, len(s), 3):
hexchars = s[i:i+2]
b = r'\x' + hexchars
bstr += b
bstr += "'"
bl1.append(eval(bstr))
# Method 2: equivalent, efficient, terse, hard to read.
bl2 = [eval("b'{}'".format(''.join(r'\x{}'.format(s[i:i+2])
for i in range(0, len(s), 3))))
for s in elist]
assert(bl1 == bl2)
if show_work:
print('bl1 = {}\n'.format(bl1))
dlist = [b.decode(encoding) for b in bl1]
if show_work:
print('dlist = {}\n'.format(dlist))
return(dlist)
result = wrapbytes('☺ ☺☺ ☺☺ ☺ ☺ ☺☺ ☺☺', bytewidth=10, show_work=True)
print('\n'.join(result))
我最终重写了 textwrap
的一部分以在拆分字符串后对单词进行编码。
与 Tom 的解决方案不同,Python 代码不需要遍历每个字符。
def byteTextWrap(text, size, break_long_words=True):
"""Similar to textwrap.wrap(), but considers the size of strings (in bytes)
instead of their length (in characters)."""
try:
words = textwrap.TextWrapper()._split_chunks(text)
except AttributeError: # Python 2
words = textwrap.TextWrapper()._split(text)
words.reverse() # use it as a stack
if sys.version_info[0] >= 3:
words = [w.encode() for w in words]
lines = [b'']
while words:
word = words.pop(-1)
if len(word) > size:
words.append(word[size:])
word = word[0:size]
if len(lines[-1]) + len(word) <= size:
lines[-1] += word
else:
lines.append(word)
if sys.version_info[0] >= 3:
return [l.decode() for l in lines]
else:
return lines
如何使用 textwrap
模块在一行达到一定字节数之前进行拆分(不拆分多字节字符)?
我想要这样的东西:
>>> textwrap.wrap('☺ ☺☺ ☺☺ ☺ ☺ ☺☺ ☺☺', bytewidth=10)
☺ ☺☺
☺☺ ☺
☺ ☺☺
☺☺
结果取决于使用的编码,因为每个字节数
字符是编码的函数,在许多编码中,
性格也是如此。我假设我们使用的是 UTF-8,其中 '☺'
是
编码为 e298ba
并且是三个字节长;给定的例子是
与该假设一致。
textwrap
中的所有内容都适用于角色;它什么都不知道
关于编码。解决此问题的一种方法是将输入字符串转换为
另一种格式,每个字符变成一串字符
其长度与字节长度成正比。我会用三个
字符:两个十六进制字节,加上一个控制换行符。
因此:
'a' -> '61x' non-breaking
' ' -> '20 ' breaking
'☺' -> 'e2x98xbax' non-breaking
为简单起见,我假设我们只在空格处打断,而不是制表符或任何 其他角色。
import textwrap
def wrapbytes(s, bytewidth, encoding='utf-8', show_work=False):
byts = s.encode(encoding)
encoded = ''.join('{:02x}{}'.format(b, ' ' if b in b' ' else 'x')
for b in byts)
if show_work:
print('encoded = {}\n'.format(encoded))
ewidth = bytewidth * 3 + 2
elist = textwrap.wrap(encoded, width=ewidth)
if show_work:
print('elist = {}\n'.format(elist))
# Remove trailing encoded spaces.
elist = [s[:-2] if s[-2:] == '20' else s for s in elist]
if show_work:
print('elist = {}\n'.format(elist))
# Decode. Method 1: inefficient and lengthy, but readable.
bl1 = []
for s in elist:
bstr = "b'"
for i in range(0, len(s), 3):
hexchars = s[i:i+2]
b = r'\x' + hexchars
bstr += b
bstr += "'"
bl1.append(eval(bstr))
# Method 2: equivalent, efficient, terse, hard to read.
bl2 = [eval("b'{}'".format(''.join(r'\x{}'.format(s[i:i+2])
for i in range(0, len(s), 3))))
for s in elist]
assert(bl1 == bl2)
if show_work:
print('bl1 = {}\n'.format(bl1))
dlist = [b.decode(encoding) for b in bl1]
if show_work:
print('dlist = {}\n'.format(dlist))
return(dlist)
result = wrapbytes('☺ ☺☺ ☺☺ ☺ ☺ ☺☺ ☺☺', bytewidth=10, show_work=True)
print('\n'.join(result))
我最终重写了 textwrap
的一部分以在拆分字符串后对单词进行编码。
与 Tom 的解决方案不同,Python 代码不需要遍历每个字符。
def byteTextWrap(text, size, break_long_words=True):
"""Similar to textwrap.wrap(), but considers the size of strings (in bytes)
instead of their length (in characters)."""
try:
words = textwrap.TextWrapper()._split_chunks(text)
except AttributeError: # Python 2
words = textwrap.TextWrapper()._split(text)
words.reverse() # use it as a stack
if sys.version_info[0] >= 3:
words = [w.encode() for w in words]
lines = [b'']
while words:
word = words.pop(-1)
if len(word) > size:
words.append(word[size:])
word = word[0:size]
if len(lines[-1]) + len(word) <= size:
lines[-1] += word
else:
lines.append(word)
if sys.version_info[0] >= 3:
return [l.decode() for l in lines]
else:
return lines