lectura de estructura en python a partir de estructura creada en c

9 minutos de lectura

Soy muy nuevo en el uso de Python y estoy muy oxidado con C, así que me disculpo de antemano por lo tonto y/o perdido que sueno.

Tengo una función en C que crea un archivo .dat que contiene datos. Estoy abriendo el archivo usando Python para leer el archivo. Una de las cosas que necesito leer es una estructura que se creó en la función C y se imprimió en binario. En mi código de Python estoy en la línea apropiada del archivo para leer en la estructura. He intentado descomprimir la estructura elemento por elemento y en su conjunto sin éxito. La mayoría de los elementos de la estructura se declararon ‘reales’ en el código C. Estoy trabajando en este código con otra persona y el código fuente principal es suyo y ha declarado las variables como ‘reales’. Necesito poner esto en un bucle porque quiero leer todos los archivos en el directorio que terminan en ‘.dat’. Para iniciar el ciclo tengo:

for files in os.listdir(path):
  if files.endswith(".dat"):
    part = open(path + files, "rb")
    for line in part:

Que luego leo todas las líneas anteriores a la que contiene la estructura. Luego llego a esa línea y tengo:

      part_struct = part.readline()
      r = struct.unpack('<d8', part_struct[0])

Estoy tratando de leer lo primero almacenado en la estructura. Vi un ejemplo de esto en algún lugar aquí. Y cuando intento esto, aparece un error que dice:

struct.error: repeat count given without format specifier

Tomaré todos y cada uno de los consejos que alguien me pueda dar. He estado atascado en esto durante unos días y he probado muchas cosas diferentes. Para ser honesto, creo que no entiendo el módulo struct pero he leído todo lo que pude sobre él.

¡Gracias!

avatar de usuario
jfs

podrías usar ctypes.Structure o struct.Struct para especificar el formato del archivo. Para leer estructuras del archivo producido por el código C en la respuesta de @perreal:

"""
struct { double v; int t; char c;};
"""
from ctypes import *

class YourStruct(Structure):
    _fields_ = [('v', c_double),
                ('t', c_int),
                ('c', c_char)]

with open('c_structs.bin', 'rb') as file:
    result = []
    x = YourStruct()
    while file.readinto(x) == sizeof(x):
        result.append((x.v, x.t, x.c))

print(result)
# -> [(12.100000381469727, 17, 's'), (12.100000381469727, 17, 's'), ...]

Ver io.BufferedIOBase.readinto(). Es compatible con Python 3 pero no está documentado en Python 2.7 por un objeto de archivo predeterminado.

struct.Struct requiere especificar bytes de relleno (x) explícitamente:

"""
struct { double v; int t; char c;};
"""
from struct import Struct

x = Struct('dicxxx')
with open('c_structs.bin', 'rb') as file:
    result = []
    while True:
        buf = file.read(x.size)
        if len(buf) != x.size:
            break
        result.append(x.unpack_from(buf))

print(result)

Produce la misma salida.

Para evitar copias innecesarias Array.from_buffer(mmap_file) podría usarse para obtener una matriz de estructuras de un archivo:

import mmap # Unix, Windows
from contextlib import closing

with open('c_structs.bin', 'rb') as file:
    with closing(mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_COPY)) as mm: 
        result = (YourStruct * 3).from_buffer(mm) # without copying
        print("\n".join(map("{0.v} {0.t} {0.c}".format, result)))

avatar de usuario
perreal

Algo de código C:

#include <stdio.h>
typedef struct { double v; int t; char c;} save_type;
int main() {
    save_type s = { 12.1f, 17, 's'};
    FILE *f = fopen("output", "w");
    fwrite(&s, sizeof(save_type), 1, f);
    fwrite(&s, sizeof(save_type), 1, f);
    fwrite(&s, sizeof(save_type), 1, f);
    fclose(f);
    return 0;
}

Algo de código Python:

import struct
with open('output', 'rb') as f:
    chunk = f.read(16)
    while chunk != "":
        print len(chunk)
        print struct.unpack('dicccc', chunk)
        chunk = f.read(16)

Producción:

(12.100000381469727, 17, 's', '\x00', '\x00', '\x00')
(12.100000381469727, 17, 's', '\x00', '\x00', '\x00')
(12.100000381469727, 17, 's', '\x00', '\x00', '\x00')

pero también está el problema del relleno. El tamaño acolchado de save_type es 16, entonces leemos 3 caracteres más y los ignoramos.

  • Observe que el tamaño de un fragmento depende del tamaño de la palabra nativa. En este caso parece ser de 32 bits. El tipo de datos real estándar ocupa 4 bytes. Un doble toma 8 bytes, entero 4 bytes y char 1 byte, en total 16 bytes. Si está haciendo esto en una máquina de 64 bits, entonces el tamaño del fragmento es de 24 bytes y la ‘cadena de formato’ correspondiente sería ‘dixxxxcxxxxxxx’ o el equivalente ‘d i4x c7x’. Consulte también los documentos de estructura de Python.

    – Alejandro

    17 de marzo a las 7:37


Un número en el especificador de formato significa un conteo de repeticiones, pero tiene que ir antes de la letra, como '<8d'. Sin embargo, dijiste que solo quieres leer un elemento de la estructura. Supongo que solo quieres '<d'. Supongo que está tratando de especificar la cantidad de bytes para leer como 8, pero no necesita hacer eso. d asume que

También noté que estás usando readline. Eso parece incorrecto para leer datos binarios. Leerá hasta el próximo retorno de carro/avance de línea, que ocurrirá aleatoriamente en datos binarios. Lo que quieres hacer es usar read(size)Me gusta esto:

part_struct = part.read(8)
r = struct.unpack('<d', part_struct)

En realidad, debe tener cuidado, ya que read puede devolver menos datos de los que solicita. Necesitas repetirlo si lo hace.

part_struct = b''
while len(part_struct) < 8:
    data = part.read(8 - len(part_struct))
    if not data: raise IOException("unexpected end of file")
    part_struct += data
r = struct.unpack('<d', part_struct)

avatar de usuario
topin89

Tuve el mismo problema recientemente, así que hice un módulo para la tarea, almacenado aquí: http://pastebin.com/XJyZMyHX

código de ejemplo:

MY_STRUCT="""typedef struct __attribute__ ((__packed__)){
    uint8_t u8;
    uint16_t u16;
    uint32_t u32;
    uint64_t u64;
    int8_t i8;
    int16_t i16;
    int32_t i32;
    int64_t i64;
    long long int lli;
    float flt;
    double dbl;
    char string[12];
    uint64_t array[5];
} debugInfo;"""

PACKED_STRUCT='\x01\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\x00\xff\x00\x00\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff*\x00\x00\x00\x00\x00\x00\x00ff\x06@\x14\xaeG\xe1z\x14\x08@testString\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00'

if __name__ == '__main__':
    print "String:"
    print depack_bytearray_to_str(PACKED_STRUCT,MY_STRUCT,"<" )
    print "Bytes in Stuct:"+str(structSize(MY_STRUCT))
    nt=depack_bytearray_to_namedtuple(PACKED_STRUCT,MY_STRUCT,"<" )
    print "Named tuple nt:"
    print nt
    print "nt.string="+nt.string

El resultado debería ser:

String:
u8:1
u16:256
u32:65536
u64:4294967296
i8:-1
i16:-256
i32:-65536
i64:-4294967296
lli:42
flt:2.09999990463
dbl:3.01
string:u'testString\x00\x00'
array:(1, 2, 3, 4, 5)

Bytes in Stuct:102
Named tuple nt:
CStruct(u8=1, u16=256, u32=65536, u64=4294967296L, i8=-1, i16=-256, i32=-65536, i64=-4294967296L, lli=42, flt=2.0999999046325684, dbl=3.01, string="u'testString\\x00\\x00'", array=(1, 2, 3, 4, 5))
nt.string=u'testString\x00\x00'

avatar de usuario
jasha

Numpy se puede usar para leer/escribir datos binarios. Sólo necesita definir una costumbre np.dtype instancia que define el diseño de la memoria de su c-struct.

Por ejemplo, aquí hay un código C++ que define una estructura (debería funcionar igual de bien para las estructuras C, aunque no soy un experto en C):

struct MyStruct {
    uint16_t FieldA;
    uint16_t pad16[3];
    uint32_t FieldB;
    uint32_t pad32[2];
    char     FieldC[4];
    uint64_t FieldD;
    uint64_t FieldE;
};

void write_struct(const std::string& fname, MyStruct h) {
    // This function serializes a MyStruct instance and
    // writes the binary data to disk.
    std::ofstream ofp(fname, std::ios::out | std::ios::binary);
    ofp.write(reinterpret_cast<const char*>(&h), sizeof(h));

}

Basado en el consejo que encontré en stackoverflow.com/a/5397638, he incluido algo de relleno en la estructura (el pad16 y pad32 campos) para que la serialización suceda de una manera más predecible. Creo que esto es algo de C++; puede que no sea necesario cuando se usan estructuras C simples.

Ahora, en python, creamos un numpy.dtype objeto que describe el diseño de la memoria de MyStruct:

import numpy as np

my_struct_dtype =  np.dtype([
    ("FieldA"            , np.uint16  ,       ),
    ("pad16"             , np.uint16  , (3,)  ),
    ("FieldB"            , np.uint32          ),
    ("pad32"             , np.uint32  , (2,)  ),
    ("FieldC"            , np.byte    , (4,)  ),
    ("FieldD"            , np.uint64          ),
    ("FieldE"            , np.uint64          ),
])

Luego usa numpy’s fromfile para leer el archivo binario donde guardó su c-struct:

# read data
struct_data = np.fromfile(fpath, dtype=my_struct_dtype, count=1)[0]

FieldA         = struct_data["FieldA"]
FieldB         = struct_data["FieldB"]
FieldC         = struct_data["FieldC"]
FieldD         = struct_data["FieldD"]
FieldE         = struct_data["FieldE"]

if FieldA != expected_value_A:
    raise ValueError("Bad FieldA, got %d" % FieldA)
if FieldB != expected_value_B:
    raise ValueError("Bad FieldB, got %d" % FieldB)
if FieldC.tobytes() != b"expc":
    raise ValueError("Bad FieldC, got %s" % FieldC.tobytes().decode())
...

los count=1 argumento en la llamada anterior np.fromfile(..., count=1) es para que la matriz devuelta tenga solo un elemento; esto significa “leer la primera instancia de estructura del archivo”. Tenga en cuenta que estoy indexando [0] para sacar ese elemento de la matriz.

Si ha agregado los datos de muchas c-structs al mismo archivo, puede usar fromfile(..., count=n) leer n instancias de estructura en una matriz numpy de forma (n,). Ajuste count=-1que es el valor predeterminado para el np.fromfile y np.frombuffer funciones, significa “leer todos los datos”, lo que da como resultado una matriz unidimensional de formas (number_of_struct_instances,).

También puede utilizar el offset argumento de palabra clave para np.fromfile para controlar en qué parte del archivo comenzará la lectura de datos.

Para concluir, aquí hay algunas funciones numpy que serán útiles una vez que su personalizado dtype ha sido definido:

  • Lectura de datos binarios como una matriz numpy:
    • np.frombuffer(bytes_data, dtype=...): Interpretar los datos binarios dados (por ejemplo, un python bytes instancia) como una matriz numpy del dtype dado. Puede definir una costumbre
      dtype que describe el diseño de la memoria de su estructura c.
    • np.fromfile(filename, dtype=...): Leer datos binarios de filename. Debería ser el mismo resultado que
      np.frombuffer(open(filename, "rb").read(), dtype=...).
  • Escribiendo una matriz numpy como datos binarios:
    • ndarray.tobytes(): Construir una pitón bytes instancia que contiene datos sin procesar de la matriz numpy dada. Si los datos de la matriz tienen dtype correspondiente a una estructura c, entonces los bytes que provienen de
      ndarray.tobytes puede ser deserializado por c/c++ e interpretado como una (matriz de) instancias de esa c-struct.
    • ndarray.tofile(filename): Los datos binarios de la matriz se escriben en filename. Estos datos luego podrían ser deserializados por c/c++. Equivalente a open("filename", "wb").write(a.tobytes()).

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad