Voici quelques conseils pour travailler avec des binaires en Python.
Il existe deux façons de travailler avec des binaires en Python, le module struct
et la classe ctypes.Structure
.
Fondamentalement, le module struct
utilise la classe ctypes.Structure
lorsque vous souhaitez gérer quelques octets de binaire, ou lorsque vous souhaitez travailler avec plus d'octets ou C / C ++.
struct
A titre d'exemple, lisons le binaire d'un fichier PNG. Dans un fichier PNG, les 8 premiers octets sont fixés dans l'en-tête. Les 9e à 18e octets de données sont stockés dans la zone IHDR (pour être exact, une partie de l'IHDR), la taille verticale et horizontale de l'image, la profondeur de bits et le mode couleur.
import struct
png_data = open("sample.png ", "rb").read()
struct.unpack_from(">I4sIIBB", png_data, 8)
# (13, b'IHDR', 250, 156, 8, 2)
Vous pouvez lire les données avec struct.unpack
, mais vous obtiendrez une erreur si le décalage et la taille du tampon que vous donnez ne sont pas exactement les mêmes.
Struct.unpack_from
est utile si vous voulez lire une partie des données.
x
Lors de la lecture du binaire, la mise (zone de poussière pour l'alignement) sort par tous les moyens. Le format «x» est pratique car il ignore les données.
data = b'd\x00\xb0\x04'
# NG
kind, _, value = struct.unpack("BBH", data)
# Yes!
kind, value = struct.unpack("BxH", data)
La classe struct.Struct
est une classification de la chaîne de format du module struct
.
Puisque le format est analysé lorsque la classe est instanciée, il est plus rapide de créer l'instance à l'avance lorsque pack
/ ʻunpackà plusieurs reprises dans la boucle. C'est déroutant avec la classe
ctypes.Structre`.
point = struct.Struct("HH")
for x, y in zip(range(10), range(10)):
point.pack(x, y)
lettre | Type de langage C | Taille standard |
---|---|---|
x | Mettre du mordant | 1 |
c | char | 1 |
b | signed char | 1 |
B | unsigned char, BYTE | 1 |
? | _Bool | 1 |
h | short | 2 |
H | unsinged short, WORD | 2 |
i | int | 4 |
I | unsigned int, DWORD | 4 |
l | long, LONG | 4 |
L | unsigned long, ULONG | 4 |
q | long long, LONGLONG | 8 |
Q | unsigned long long, ULONGLONG | 8 |
n | ssize_t(Python3.3 ou plus tard) | Native uniquement |
N | size_t(Python3.3 ou plus tard) | Native uniquement |
f | float | 4 |
d | double | 8 |
s | char[] | - |
p | char[] | - |
P | void * | - |
Exemple de caractère de format:
Structure de BITMAPINFOHEADER
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
Format des caractères pour la structure BITMAPINFOHEADER
"IllHHIIllII"
lettre | Ordre des octets | Taille | alignement |
---|---|---|---|
@ | Native | Native | Native |
= | Native | Taille standard | Aucun |
< | Petit indien | Taille standard | Aucun |
> | Big endian | Taille standard | Aucun |
! | Big endian | Taille standard | Aucun |
@
@Quand=La différence de(CPU=amd64,OS=Ubuntu64bit)
struct.calcsize("BI")
# 8
struct.calcsize("=BI")
# 5
Notez que si vous spécifiez explicitement l'endian, l'alignement sera "aucun".
Vous pouvez travailler avec des structures C / C ++ dans la classe ctypes.Structure
.
Si vous essayez de lire beaucoup de données avec le module'struct ', le format sera comme un sort, donc si vous voulez écrire fermement beaucoup de données binaires en lecture, vous devriez utiliser la classe ctypes.Structure
. Faisons le.
Hériter de ctypes.Structure
et définir les types dans _field_
.
from ctypes import *
"""
typedef struct {
char identity[4];
uint16_t x;
uint16_t y;
} TestStructure;
"""
class TestStructure(Structure):
_fields_ = (
('identity', c_char * 4),
('x', c_uint16),
('y', c_uint16),
)
L'instance est définie comme suit.
t = TestStructure(b"TEST", 100, 100)
En langage C, la taille de ʻintet
short change en fonction de l'environnement.De C99, il est possible de spécifier des types de taille fixe tels que ʻint16_t
et ʻint32_t, donc spécifiez la taille fixe autant que possible. Devrait être utilisé. Parallèlement à cela, utilisons un type de taille fixe tel que
ctypes.c_int16 au lieu de
ctypes.c_int` du côté Python.
Vous pouvez écrire en passant l'instance ctypes.Structure
telle quelle pour écrire
de ʻio ou de
FILE`.
import io
buffer = io.BytesIO()
buffer.write(TestStructure(b"TEST", 100, 100))
buffer.getvalue()
# b'TESTd\x00d\x00'
Vous pouvez le lire en passant l'instance ctypes.Structure
à readinto
telle quelle.
buffer = io.BytesIO(b'TESTd\x00d\x00')
t = TestStructure()
buffer.readinto(t)
t.identity, t.x, t.y
# (b'TEST', 100, 100)
La position de décalage du membre de la structure peut être obtenue par la méthode de classe nom de classe.membre nom.offset
.
class Point(Structure):
_fields_ = (
('x', c_uint16),
('y', c_uint16),
)
Point.y.offset
# 2
sizeof
Vous pouvez obtenir la taille de la structure avec ctypes.sizeof
.
class TestStructure(Structure):
_fields_ = (
('flags', c_ubyte),
('value', c_int32),
)
sizeof(TestStructure)
# 8
memset / memmove Les équivalents du langage C «memset» et «memmove» sont «ctypes.memset» et «ctypes.memmove».
c_array = (c_char * 12)()
memset(c_array, 0, sizeof(c_array))
memmove(c_array, b"test\x00", len(b"test\x00"))
Vous pouvez mapper les données en castant les pointeurs de la structure comme en C / C ++.
Si vous souhaitez spécifier le pointeur de la structure, transtypez-le avec ctypes.POINTER
, ctypes.cast
. Vous pouvez obtenir la valeur référencée par le pointeur avec contents
.
class PointText(Structure):
_fields_ = (
('x', c_uint16),
('y', c_uint16),
('text', c_char * 0),
)
data = b'd\x00d\x00null terminate text\x00'
p_point = cast(data, POINTER(Point))
p_point.contents.x, p_point.contents.y
# (200, 120)
#Lire la chaîne de caractères terminée par NULL
string_at(addressof(p_point.contents) + PointText.text.offset)
# b'null terminate text'
Lisez la chaîne terminée par NULL avec ctypes.stering_at
, et utilisez ctypes.wstring_at
pour Unicode.
Cependant, sachez que la manipulation du pointeur peut faire planter Python lui-même, et vous devez éviter les membres non spécifiés tels que char []
si possible.
Vous pouvez convertir un objet ctypes
en un PyObject
avec memoryview
.
p = Point(200, 120)
memoryview(p).tobytes()
# b'\xc8\x00x\x00'
class BPoint(BigEndianStructure):
_fields_ = (
('x', c_uint16),
('y', c_uint16),
)
class LPoint(LittleEndianStructure):
_fields_ = (
('x', c_uint16),
('y', c_uint16),
)
bpoint = BPoint(0x0102, 0x0304)
lpoint = LPoint(0x0102, 0x0304)
memoryview(bpoint).tobytes()
# b'\x01\x02\x03\x04'
memoryview(lpoint).tobytes()
# b'\x02\x01\x04\x03'
http://docs.python.jp/3.5/library/struct.html http://docs.python.jp/3.5/library/ctypes.html
Recommended Posts