web-dev-qa-db-fra.com

emballage et décompression tableau/chaîne de longueur variable en utilisant le module struct en python

J'essaie de maîtriser le compactage et le décompactage des données binaires dans Python 3. Ce n'est en fait pas si difficile à comprendre, sauf un problème:

que se passe-t-il si j'ai une chaîne de texte de longueur variable et que je veux emballer et décompresser celle-ci de la manière la plus élégante?

D'après ce que je peux dire dans le manuel, je ne peux décompresser que les chaînes de taille fixe directement? Dans ce cas, y a-t-il un moyen élégant de contourner cette limitation sans ajouter beaucoup de zéros inutiles?

29
agnsaft

Le module struct ne prend en charge que les structures de longueur fixe. Pour les chaînes de longueur variable, vos options sont les suivantes:

  • Construisez dynamiquement votre chaîne de format (une str devra être convertie en une bytes avant de la passer à pack()): 

    s = bytes(s, 'utf-8')    # Or other appropriate encoding
    struct.pack("I%ds" % (len(s),), len(s), s)
    
  • Ignorez struct et utilisez simplement les méthodes de chaîne habituelles pour ajouter la chaîne à votre sortie pack()- ed: struct.pack("I", len(s)) + s

Pour décompresser, il suffit de décompresser un à un:

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

Si vous faites beaucoup de cela, vous pouvez toujours ajouter une fonction d'assistance qui utilise calcsize pour effectuer le découpage en chaîne:

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

J'ai googlé cette question et quelques solutions.

construire

Une solution élaborée et flexible.

Au lieu d'écrire du code impératif pour analyser une donnée, vous définissez de manière déclarative une structure de données décrivant vos données. Comme cette structure de données n'est pas du code, vous pouvez l'utiliser dans un sens pour analyser des données dans des objets Pythonic, et dans l'autre sens, convertir des objets («construits») en données binaires.

La bibliothèque fournit à la fois des constructions atomiques simples (telles que des entiers de différentes tailles) et des constructions composites vous permettant de former des structures hiérarchiques de complexité croissante. Construisez des caractéristiques de granularité en bits et en octets, un débogage et des tests faciles, un système de sous-classe facile à étendre et de nombreuses constructions primitives pour faciliter votre travail:

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

Une solution rapide si vous n’avez besoin que d’une extension struct pour les séquences d’octets de longueur variable. L'imbrication d'une structure de longueur variable peut être réalisée en packing les premiers résultats pack.

NetStruct prend en charge un nouveau caractère de mise en forme, le signe dollar ($). Le signe dollar représente une chaîne de longueur variable, codée avec sa longueur précédant la chaîne elle-même.

edit: on dirait que la longueur d'une chaîne de longueur variable utilise le même type de données que les éléments. Ainsi, la longueur maximale d'une chaîne d'octets de longueur variable est de 255, si mots - 65535, etc.

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

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

Voici quelques fonctions de wrapper que j'ai écrites qui aident, elles semblent fonctionner.

Voici l'aide de déballage:

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

Voici l'assistant d'emballage:

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

Pour emballer utiliser 

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

Pour décompresser utiliser

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

Cela ne fonctionne que sur la chaîne utf-8 et une solution de contournement assez simple.

1
Santosh Kale

Une manière simple de pouvoir faire une longueur variable lorsque vous composez une chaîne est la suivante:

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

lors du déballage, c'est un peu de la même manière

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

Bien, mais ne peut pas gérer le nombre de champs numériques, tels que '6B' pour 'BBBBBB' La solution consisterait à développer la chaîne de format dans les deux fonctions avant utilisation. Je suis venu avec ceci:

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

Et pareil pour décompresser. Peut-être pas le plus élégant, mais ça marche :)

0
Vladimir Talybin