質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
バイナリ

バイナリは、「0」と「1」だけで表現されている2進数のデータ形式。または、テキスト以外の情報でデータが記述されているファイルを指します。コンピューター内の処理は全て2進数で表記されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2275閲覧

pythonのstructにバイナリデータを保存する方法

gomigorou

総合スコア7

バイナリ

バイナリは、「0」と「1」だけで表現されている2進数のデータ形式。または、テキスト以外の情報でデータが記述されているファイルを指します。コンピューター内の処理は全て2進数で表記されています。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

2クリップ

投稿2020/04/22 03:18

pythonのstructにバイナリデータを保存しようとしています。
コードは下記の通りです。

iniHead関数内で、head.passwdio.BytesIOを用いてバイナリデータを代入しているのですが、\x00で途切れてしまい、データ全てを保存できません。

base64にエンコードすれば、データ全てを保存できるのはわかるのですが、エンコードするとデータサイズが大きくなるのと、余計な処理は追加したくないので、できれば避けたいです。

何か良い方法はありますでしょうか?

python

1from ctypes import * 2import struct 3import io 4import base64 5 6PASS_SIZE = 512 7bin_data = b'\x03\x02\x01\x00\x01\x02\x03' # バイナリデータ 8 9class Head(LittleEndianStructure): 10 _pack_ = 1 11 _fields_ = [ 12 ('id', c_char * 3), 13 ('passwd', c_char * PASS_SIZE), 14 ] 15 16def iniHead(): 17 head = Head() 18 head.id = b'ABC' 19 head.passwd = io.BytesIO(bin_data).read() # バイナリデータを保存 20 return head 21 22if __name__== "__main__": 23 head = iniHead() 24 25 # \x00でデータが途切れてしまう 26 print("head.passwd: {}".format(head.passwd)) # head.passwd: b'\x03\x02\x01' 27 28 # structでなければ、\x00以降も保存される 29 notHeadPasswd = io.BytesIO(bin_data).read() 30 print("notHeadPasswd: {}".format(notHeadPasswd)) # notHeadPasswd: b'\x03\x02\x01\x00\x01\x02\x03' 31 32 # base64にエンコードすれば保存はできる (できれば使いたくない) 33 b64_encode_data = base64.b64encode(bin_data) 34 head.passwd = b64_encode_data 35 b64_decode_data = base64.b64decode(head.passwd) 36 print("b64_decode_data: {}".format(b64_decode_data)) # b64_decode_data: b'\x03\x02\x01\x00\x01\x02\x03' 37

実行結果

head.passwd: b'\x03\x02\x01' notHeadPasswd: b'\x03\x02\x01\x00\x01\x02\x03' b64_decode_data: b'\x03\x02\x01\x00\x01\x02\x03'
DrqYuto👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Kenji.Noguchi

2020/04/22 05:36

structは基本的に固定長で使う物で、もし可変長のデータを扱いたいなら、そのデータを確保したメモリのアドレスへのポインタを持つしかないです。データサイズを気にされていますが、sizeof(head)するとわかりますが現状のコードでは最大長の514バイトが確保されています。 なぜctypeのstructを使いたいのですか?ネットワークやディスクなどからバイト列を読み書きしたいとか、そう言う目的でしょうか?一般に可変長のデータはサイズの情報が先頭にあるか、区切り文字の指定がないと、どれだけ読み込んだら良いのかがわかりません。このあたりの背景が明確になれば解決法はあると思います。\0で切れるのはヌル終端文字列として扱われているからでしょう。
gomigorou

2020/04/22 07:32

ご助言ありがとうございます。 目的は、バイト列が組み込まれたファイルを読み書きする事です。 そのファイルは、Pythonだけではなく、C言語などのプログラムでも扱える様に互換性を保つ必要があります。 データの内容自体は可変ですが、サイズは固定長です。 なので、 ```head.passwd = io.BytesIO(bin_data).read(PASS_SIZE)``` と、読み込むサイズを指定しても問題無いかと思います。(bin_dataはあくまでサンプルで、実際はもっと長いです) ですがこの様にしても、やはり\x00で途切れてしまいます。
guest

回答1

0

ベストアンサー

補足により固定長で良いとのことなので、こんな感じです。サイズの情報がないと正しい情報が読み出せないので追加しました。

Python

1from ctypes import * 2import io 3 4DATA_SIZE = 20 5bin_data = b'\x03\x02\x01\x00\x01\x02\x03' # バイナリデータ 6 7 8class Head(LittleEndianStructure): 9 _pack_ = 1 10 _fields_ = [ 11 ('data', c_ubyte * DATA_SIZE), 12 ('data_len', c_int), 13 ] 14 15 16def iniHead(): 17 head = Head() 18 data = io.BytesIO(bin_data).read() 19 head.data = (c_ubyte * DATA_SIZE)(*data) 20 head.data_len = len(data) 21 return head 22 23 24head = iniHead() 25print("Struct size", sizeof(head)) 26print("Buffer data len", len(head.data)) 27print("Buffer data", head.data[:]) 28print("Actual data len", head.data_len) 29print("Actual data", head.data[:head.data_len])

結果

Struct size 24 Buffer data len 20 Buffer data [3, 2, 1, 0, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] Actual data len 7 Actual data [3, 2, 1, 0, 1, 2, 3]

これから設計をするなら、AvroやProtobufのように多言語で使えて、スキーマを定義したら各言語でバインディング(タイプクラス、シリアライザー、デシリアライザー)の生成ができるライブラリを使うのが良いでしょう。

例えはstructに新たにフィールドを追加したかったらどうしますか?レコード長が変わってしまいますが、全システムを同時に更新できますか?多分無理でしょう。後方互換性を確保しつつ変更する方法を考えないといけないですね。固定長で良いとのことですが、フィールドが増える度にサイズが増えますね。全てのフィールドを使っても使っていなくてもスペースを消費してしまいます。

このあたりの問題を解決してくれるのが先に挙げたようなライブラリで提供されるRPCフォーマットです。ご参考まで。

投稿2020/04/22 16:33

編集2020/04/22 19:02
Kenji.Noguchi

総合スコア358

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

gomigorou

2020/04/23 03:09

ご回答ありがとうございます! わかりやすい解説とソースコード、RPCフォーマットのご紹介など、大変勉強になりました!! ```(c_ubyte * DATA_SIZE)(*data)```の様に、pythonでも型キャストができるんですね!
Kenji.Noguchi

2020/04/23 04:09

この型変換はint("3")が3になるのと基本的に同じです。特殊なのは型が動的に生成されている点で、c_ubyte * 512はc_ubyte_ARRAY_512という型を作っています。そして型変換を()で実行しています。ctypesはPythonの世界の外とやりとりするための手段なので何かとPythonの知識が通じないことが多いですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問