Lectura de enteros de un archivo binario en Python

82

Estoy intentando leer un archivo BMP en Python. Sé que los dos primeros bytes indican la firma BMP. Los siguientes 4 bytes son el tamaño del archivo. Cuando ejecuto:

fin = open("hi.bmp", "rb")
firm = fin.read(2)  
file_size = int(fin.read(4))  

Yo obtengo:

ValueError: literal no válido para int () con base 10: 'F # \ x13'

Lo que quiero hacer es leer esos cuatro bytes como un número entero, pero parece que Python los está leyendo como caracteres y devuelve una cadena, que no se puede convertir en un número entero. ¿Cómo puedo hacer esto correctamente?

Manuel Araoz
fuente
2
Si su objetivo es usar el mapa de bits en lugar de dedicar tiempo a escribir su propia biblioteca BMP (no es que eso no suene divertido ...) puede usar PIL pythonware.com/products/pil, que puede que ya haya instalado. Pruebe: imagen de importación
Jared Updike
7
Gracias Jared, ¡pero quería leer el bmp manualmente solo para divertirme! :)
Manuel Araoz

Respuestas:

123

El readmétodo devuelve una secuencia de bytes como una cadena. Para convertir de una secuencia de bytes de cadena a datos binarios, use el structmódulo incorporado : http://docs.python.org/library/struct.html .

import struct

print(struct.unpack('i', fin.read(4)))

Tenga en cuenta que unpacksiempre devuelve una tupla, por lo que struct.unpack('i', fin.read(4))[0]proporciona el valor entero que busca.

Probablemente debería usar la cadena de formato '<i'(<es un modificador que indica el orden de bytes little-endian y el tamaño y alineación estándar; el valor predeterminado es usar el orden de bytes, el tamaño y la alineación de la plataforma). Según la especificación del formato BMP, los bytes deben escribirse en orden de bytes Intel / little-endian.

codeape
fuente
22
En lugar de escribir i = struct.unpack(...)[0], escribo a menudoi, = struct.unpack(...)
Otto Allmendinger
@Otto ¿Hay alguna razón por la que prefieras una forma sobre la otra? ¿Existe alguna diferencia lógica?
Caltor
2
Me parece muy sorprendente que no haya una función incorporada para leer números enteros (o cortos, etc.) de un archivo en Python. No soy un experto en Java, pero creo que tiene funciones nativas como readUnsignedShort () para hacer esto.
Caltor
@codeape ¿Podrías definir qué hace el [0] por favor o al menos qué tipo de elemento de lenguaje es? No es evidente de inmediato y es casi imposible buscarlo en la documentación de Python.
Caltor
Para listas y tuplas, obj [N] significa: obtener el elemento N de obj. Consulte docs.python.org/tutorial/introduction.html#lists
codeape
50

Un método alternativo que no hace uso de 'struct.unpack ()' sería usar NumPy :

import numpy as np

f = open("file.bin", "r")
a = np.fromfile(f, dtype=np.uint32)

'dtype' representa el tipo de datos y puede ser int #, uint #, float #, complex # o un tipo definido por el usuario. Ver numpy.fromfile.

Personalmente, prefiero usar NumPy para trabajar con datos de matriz / matriz, ya que es mucho más rápido que usar listas de Python.

Emanuel Ey
fuente
13
La apertura del archivo se puede esquiar:a = np.fromfile('file.bin', dtype=np.uint32)
Mathieu Schopfer
16

A partir de Python 3.2+, también puede lograr esto usando el from_bytesmétodo int nativo:

file_size = int.from_bytes(fin.read(2), byteorder='big')

Tenga en cuenta que esta función requiere que especifique si el número está codificado en formato big- o little-endian, por lo que tendrá que determinar el endian-ness para asegurarse de que funciona correctamente.

Crepe De Cabra
fuente
6

Excepto structque también puedes usar el arraymódulo

import array
values = array.array('l') # array of long integers
values.read(fin, 1) # read 1 integer
file_size  = values[0]
Nick Dandoulakis
fuente
Buen punto. Pero esta solución no es tan flexible como la del módulo struct, ya que todos los elementos leídos a través de values.read () deben ser enteros largos (no es conveniente leer un entero largo, un byte y luego un entero largo, con la módulo de matriz).
Eric O Lebigot
Estoy de acuerdo. arrayes una forma eficiente de leer un archivo binario pero no muy flexible cuando tenemos que lidiar con la estructura, como mencionaste correctamente.
Nick Dandoulakis
1
array.read está en desuso en favor de array.fromfile desde 1.51
4

Mientras lee el archivo binario, debe descomprimirlo en un número entero, así que use el módulo struct para eso

import struct
fin = open("hi.bmp", "rb")
firm = fin.read(2)  
file_size, = struct.unpack("i",fin.read(4))
Anurag Uniyal
fuente
struct.unpack devuelve una tupla
luc
1

Cuando lee de un archivo binario, se usa un tipo de datos llamado bytes. Esto es un poco como una lista o una tupla, excepto que solo puede almacenar enteros de 0 a 255.

Tratar:

file_size = fin.read(4)
file_size0 = file_size[0]
file_size1 = file_size[1]
file_size2 = file_size[2]
file_size3 = file_size[3]

O:

file_size = list(fin.read(4))

En vez de:

file_size = int(fin.read(4))
Super S
fuente