2016-03-27 4 views
4

Wie kann ich das textwrap-Modul verwenden, um zu teilen, bevor eine Zeile eine bestimmte Anzahl von Bytes erreicht (ohne ein Multi-Byte-Zeichen zu teilen)?Verwenden von Textwrap mit Byteanzahl

Ich würde so etwas wie dies mag:

>>> textwrap.wrap('☺ ☺☺ ☺☺ ☺ ☺ ☺☺ ☺☺', bytewidth=10) 
☺ ☺☺ 
☺☺ ☺ 
☺ ☺☺ 
☺☺ 

Antwort

0

Ich landete einen Teil textwrap Umschreiben von Wörtern zu kodieren, nachdem es die Zeichenfolge aufgeteilt.

Im Gegensatz zu Toms Lösung muss der Python-Code nicht jedes Zeichen durchlaufen.

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 
1

Das Ergebnis hängt von der Codierung verwendet wird, da die Anzahl der Bytes pro Zeichen eine Funktion der Codierung ist, und in vielen Codierungen des Charakter auch. Ich nehme an, wir verwenden UTF-8, in dem '☺' ist codiert als e298ba und ist drei Bytes lang; Das gegebene Beispiel ist konsistent mit dieser Annahme.

Alles in textwrap funktioniert auf Zeichen; es weiß nichts über Kodierungen. Eine Möglichkeit besteht darin, die Eingabezeichenfolge in ein anderes Format zu konvertieren, wobei jedes Zeichen eine Zeichenfolge wird, deren Länge proportional zur Bytelänge ist. Ich werde drei Zeichen verwenden: zwei für das Byte in Hex, plus eins, um Zeilenumbruch zu steuern. So :

'a' -> '61x'   non-breaking 
' ' -> '20 '   breaking 
'☺' -> 'e2x98xbax' non-breaking 

Der Einfachheit halber nehme an, ich, dass wir nur auf Räume brechen, nicht Tabs oder jede anderen Charakter.

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))