2010-09-20 26 views
23

Estoy tratando de controlar el embalaje y desempaquetado de datos binarios en Python 3. Realmente no es tan difícil de entender, excepto un problema:empaquetado y desempaquetado array/string de longitud variable utilizando el módulo struct en python

¿Qué sucede si tengo una longitud de texto variable y quiero empacar y descomprimir esto de la manera más elegante?

Por lo que puedo decir del manual, solo puedo desempaquetar cadenas de tamaño fijo directamente? En ese caso, ¿hay alguna forma elegante de evitar esta limitación sin rellenar muchos ceros innecesarios?

Respuesta

17

El módulo struct solo admite estructuras de longitud fija. Para cadenas de longitud variable, sus opciones son:

  • construir dinámicamente su cadena de formato (un str tendrá que ser convertido a una bytes antes de pasarla a pack()):

    s = bytes(s, 'utf-8') # Or other appropriate encoding 
    struct.pack("I%ds" % (len(s),), len(s), s) 
    
  • Saltar struct y sólo tiene que utilizar métodos normales de cadenas para agregar la cadena a la salida de pack() -ed: struct.pack("I", len(s)) + s

para desempaquetar, sólo hay que descomprimir un poco a la vez:

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

Si estás haciendo un montón de esto, siempre se puede añadir una función de ayuda que utiliza calcsize para hacer el corte en rodajas cadena:

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

si agrega la longitud/charcount a los datos binarios, ¿cómo lo desempacaría? – agnsaft

+1

La pregunta del OP menciona específicamente Python 3, y esta respuesta no funciona en Python 3 porque los objetos de cadena ya no son compatibles con la interfaz del búfer. – jonesy

+0

@jonesy: la única parte que no funcionó fue el primer fragmento, pasando un 'str' al' paquete() '; esto ahora ha sido abordado. –

3

Aquí hay algunas funciones de envoltorio que escribí que ayudan, parecen funcionar.

Aquí está el ayudante de desembalaje:

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 

Aquí está el ayudante de embalaje:

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 
0

Niza, pero no puede manejar el número numérica de campos, tales como '6B' para 'BBBBBB'. La solución sería ampliar la cadena de formato en ambas funciones antes de su uso. Se me ocurrió esto:

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

Y lo mismo para unpack. Tal vez no es muy elegante, pero funciona :)

1

He buscado esta pregunta en Google y un par de soluciones.

construct

Una solución elaborada, flexible.

En lugar de escribir un código imperativo para analizar un dato, usted declarativamente define una estructura de datos que describe sus datos. Como esta estructura de datos no es un código, puede usarla en una dirección para analizar datos en objetos Pythonic, y en la otra dirección, convertir ("generar") objetos en datos binarios.

La biblioteca proporciona tanto construcciones atómicas simples (como enteros de varios tamaños) como compuestas que le permiten formar estructuras jerárquicas de complejidad creciente. Construir operaciones de bits y granularidad de bytes, fácil depuración y pruebas, un sistema subclase fácil de extender, y un montón de construcciones primitivas para hacer su trabajo más fácil:

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

Una solución rápida si solo necesita una extensión struct para secuencias de bytes de longitud variable. Se puede lograr anidar una estructura de longitud variable por pack con los primeros resultados de pack.

NetStruct admite un nuevo carácter de formato, el signo de dólar ($). El signo de dólar representa una cadena de longitud variable, codificada con su longitud anterior a la cadena misma.

no estoy seguro, pero parece que sólo utiliza un byte para una longitud de cuerda.

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

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

Una manera fácil de que yo era capaz de hacer una variable longitud al empacar una cadena es:

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

al desembalar es una especie de la misma manera

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

para empacar use

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

Desempaquetar use

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

Esto funciona solo en cadena utf-8 y una solución bastante simple.

Cuestiones relacionadas