it-swarm.com.de

packen und Entpacken von Array/String variabler Länge mit dem struct-Modul in Python

Ich versuche, das Packen und Entpacken von binären Daten in Python 3 in den Griff zu bekommen. Das ist eigentlich nicht so schwer zu verstehen, abgesehen von einem Problem:

was ist, wenn ich einen Textstring mit variabler Länge habe und diesen auf elegante Weise packen und auspacken möchte?

Soweit ich im Handbuch erkennen kann, kann ich nur Zeichenfolgen mit fester Größe direkt auspacken? Gibt es in diesem Fall eine elegante Möglichkeit, diese Einschränkung zu umgehen, ohne viele, unnötige Nullen aufzufüllen?

29
agnsaft

Das Modul struct unterstützt nur Strukturen mit fester Länge. Für Zeichenfolgen mit variabler Länge sind folgende Optionen verfügbar:

  • Konstruieren Sie dynamisch Ihre Formatzeichenfolge (eine str muss vor der Übergabe an pack() in eine bytes konvertiert werden): 

    s = bytes(s, 'utf-8')    # Or other appropriate encoding
    struct.pack("I%ds" % (len(s),), len(s), s)
    
  • Überspringen Sie struct und verwenden Sie normale String-Methoden, um den String Ihrer pack()- Ausgabe hinzuzufügen: struct.pack("I", len(s)) + s

Zum Auspacken müssen Sie nur ein bisschen nach dem anderen auspacken:

(i,), data = struct.unpack("I", data[:4]), data[4:]
s, data = data[:i], data[i:]

Wenn Sie viel davon tun, können Sie immer eine Hilfsfunktion hinzufügen, die calcsize für das String-Slicing verwendet:

def unpack_helper(fmt, data):
    size = struct.calcsize(fmt)
    return struct.unpack(fmt, data[:size]), data[size:]
25
llasram

Ich habe diese Frage und ein paar Lösungen gegoogelt.

konstruieren

Eine durchdachte, flexible Lösung.

Anstatt imperativen Code zu schreiben, um Daten zu parsen, definieren Sie deklarativ eine Datenstruktur, die Ihre Daten beschreibt. Da es sich bei dieser Datenstruktur nicht um Code handelt, können Sie sie in eine Richtung verwenden, um Daten in Pythonic-Objekte zu analysieren, und in die andere Richtung konvertieren ("build") Objekte in binäre Daten.

Die Bibliothek bietet sowohl einfache, atomare Konstrukte (z. B. ganze Zahlen verschiedener Größe) als auch zusammengesetzte Konstrukte, mit denen Sie hierarchische Strukturen mit zunehmender Komplexität bilden können. Konstruieren Sie die Bit- und Byte-Granularität, einfaches Debuggen und Testen, ein einfach zu erweiterndes Unterklassensystem und viele primitive Konstrukte, die Ihnen die Arbeit erleichtern:

from construct import *

PascalString = Struct("PascalString",
    UBInt8("length"),
    Bytes("data", lambda ctx: ctx.length),
)

>>> PascalString.parse("\x05helloXXX")
Container({'length': 5, 'data': 'hello'})
>>> PascalString.build(Container(length = 6, data = "foobar"))
'\x06foobar'


PascalString2 = ExprAdapter(PascalString,
    encoder = lambda obj, ctx: Container(length = len(obj), data = obj),
    decoder = lambda obj, ctx: obj.data
)

>>> PascalString2.parse("\x05hello")
'hello'
>>> PascalString2.build("i'm a long string")
"\x11i'm a long string"

netstruct

Eine schnelle Lösung, wenn Sie nur eine struct-Erweiterung für Bytefolgen mit variabler Länge benötigen. Die Verschachtelung einer Struktur mit variabler Länge kann durch packing der ersten pack-Ergebnisse erreicht werden.

NetStruct unterstützt ein neues Formatierungszeichen, das Dollarzeichen ($). Das Dollarzeichen steht für eine Zeichenfolge mit variabler Länge, deren Länge vor der Zeichenfolge selbst steht.

edit: Anscheinend verwendet die Länge einer Zeichenfolge mit variabler Länge denselben Datentyp wie die Elemente. Daher ist die maximale Länge einer Bytekette mit variabler Länge 255, wenn die Wörter - 65535 usw. sind.

import netstruct
>>> netstruct.pack(b"b$", b"Hello World!")
b'\x0cHello World!'

>>> netstruct.unpack(b"b$", b"\x0cHello World!")
[b'Hello World!']
4

Hier sind einige Wrapper-Funktionen, die ich geschrieben habe, deren Hilfe scheinbar funktioniert.

Hier ist der Auspackhelfer:

def unpack_from(fmt, data, offset = 0):
    (byte_order, fmt, args) = (fmt[0], fmt[1:], ()) if fmt and fmt[0] in ('@', '=', '<', '>', '!') else ('@', fmt, ())
    fmt = filter(None, re.sub("p", "\tp\t",  fmt).split('\t'))
    for sub_fmt in fmt:
        if sub_fmt == 'p':
            (str_len,) = struct.unpack_from('B', data, offset)
            sub_fmt = str(str_len + 1) + 'p'
            sub_size = str_len + 1
        else:
            sub_fmt = byte_order + sub_fmt
            sub_size = struct.calcsize(sub_fmt)
        args += struct.unpack_from(sub_fmt, data, offset)
        offset += sub_size
    return args

Hier ist der Verpackungshelfer:

def pack(fmt, *args):
    (byte_order, fmt, data) = (fmt[0], fmt[1:], '') if fmt and fmt[0] in ('@', '=', '<', '>', '!') else ('@', fmt, '')
    fmt = filter(None, re.sub("p", "\tp\t",  fmt).split('\t'))
    for sub_fmt in fmt:
        if sub_fmt == 'p':
            (sub_args, args) = ((args[0],), args[1:]) if len(args) > 1 else ((args[0],), [])
            sub_fmt = str(len(sub_args[0]) + 1) + 'p'
        else:
            (sub_args, args) = (args[:len(sub_fmt)], args[len(sub_fmt):])
            sub_fmt = byte_order + sub_fmt
        data += struct.pack(sub_fmt, *sub_args)
    return data
3
duncan.forster

Zu packen verwenden 

packed=bytes('sample string','utf-8')

Auspacken verwenden

string=str(packed)[2:][:-1]

Dies funktioniert nur bei utf-8-Zeichenfolgen und recht einfachen Problemumgehungen.

1
Santosh Kale

Eine einfache Möglichkeit, eine variable Länge beim Packen einer Zeichenfolge auszuführen, ist:

pack('{}s'.format(len(string)), string)

beim Auspacken ist es auf die gleiche Weise

unpack('{}s'.format(len(data)), data)
1
locus2k

Schön, kann aber nicht mit einer numerischen Anzahl von Feldern umgehen, z. B. "6B" für "BBBBBB". Die Lösung wäre, die Formatzeichenfolge in beiden Funktionen vor der Verwendung zu erweitern. Ich kam dazu:

def pack(fmt, *args):
  fmt = re.sub('(\d+)([^\ds])', lambda x: x.group(2) * int(x.group(1)), fmt)
  ...

Und das gleiche beim Auspacken. Vielleicht nicht besonders elegant, aber es funktioniert :)

0