LoginSignup
2
2

More than 1 year has passed since last update.

pythonの数値・bytes相互変換(+おまけ:bytesを誤ってstr変換して保存してしまった場合)

Last updated at Posted at 2021-03-29

はじめに

  • intであれば、to_bytes, from_bytesを使ってもよい。
  • ただしこの方法は、floatの場合は使えないなどのケースがあるため、structを使った方法を紹介する。
  • 以降、数値はfloat 32bit little endianを例に記述する。
  • 別の数値データを扱う場合は、"<f"の場所を必要に応じて変更すること。

import

import struct

float ⇔ bytes

structのpack/unpackで実現できる。

  • float ⇒ bytes
val = 123.4
struct.pack('<f', val)
# Out: b'\xcd\xcc\xf6B'
  • bytes ⇒ float
struct.unpack('<f', b'\xcd\xcc\xf6B')[0] # tuple型になるので0番目を取り出す。
# Out: 123.4000015258789

float配列 ⇔ bytes(要素数がわかっている場合)

"<ff"と必要な要素数分fを繰り返し書けば変換できる。

  • float配列 ⇒ bytes
vals = [123.4, 234.5]
struct.pack('<ff', *vals)
# Out: b'\xcd\xcc\xf6B\x00\x80jC'
  • bytes ⇒ float配列
list(struct.unpack('<ff', b'\xcd\xcc\xf6B\x00\x80jC')) # tuple型になるのでlistに変換
# Out: [123.4000015258789, 234.5]

float配列 ⇔ bytes(要素数が分かっていない場合)

  • float配列 ⇒ bytes

structのpackにbyte連結のjoinを組み合わせる。

vals = [123.4, 234.5, 345.6, 456.7]
b''.join([struct.pack('<f', v) for v in vals])
# Out: b'\xcd\xcc\xf6B\x00\x80jC\xcd\xcc\xacC\x9aY\xe4C'
  • bytes ⇒ float配列

structのiter_unpackで'<f'のサイズ分ずつunpackできるので、これをlist内包表記と組み合わせる。

[i[0] for i in struct.iter_unpack('<f', b'\xcd\xcc\xf6B\x00\x80jC\xcd\xcc\xacC\x9aY\xe4C')]
# Out: [123.4000015258789, 234.5, 345.6000061035156, 456.70001220703125]

おまけ

  • もし、以下のようにbytes配列をstrにしてファイル出力してしまった場合に元に戻したい。
vals = [123.4, 234.5, 345.6, 456.7]
bytes_data1 = b''.join([struct.pack('<f', v) for v in vals])
bytes_data1
# Out: b'\xcd\xcc\xf6B\x00\x80jC\xcd\xcc\xacC\x9aY\xe4C'

str_data = str(bytes_data1)
str_data
# Out: "b'\\xcd\\xcc\\xf6B\\x00\\x80jC\\xcd\\xcc\\xacC\\x9aY\\xe4C'" # これでファイルに書き込んでしまった!!汗
  • 以下のような関数で逆変換ができる。
    • 実行方法はコード内コメントを参照。
def str_to_bytes(str_data):

    # 前後の"とbを削除してencodeする。
    bytes_data = str_data.encode()[2:-2]

    bytes_data = b""
    for idx, _b in enumerate(str_data):
        if _b == 92 or _b == 120: # \とxはスキップ
            continue
        else:
            # \xの直後は、2文字で1byte情報となっている前半部分。ここで文字列を数値に変換する。
            if str_data[idx-2] == 92 and str_data[idx-1] == 120:
                a = str_data[idx]
                b = str_data[idx+1]

                # 一旦16新数文字列に変換し、整数にする
                value = int("0x{}{}".format(chr(a), chr(b)), 0) # chrはアスキーコード(int)を文字に変換する

                value = value.to_bytes(length=1, byteorder="little", signed=False)
                bytes_data = bytes_data + value

            # \xの2個後ろは、2文字で1byte情報となっている後半部分。変換済みなのでスキップ
            elif str_data[idx-2] == 120 and str_data[idx-3] == 92: # 2
                continue

            # どちらでもない場合、1文字で1byteの情報となっているため、そのまま数値として読み込みbyte変換する。
            else:
                value = str_data[idx]
                value = value.to_bytes(length=1, byteorder="little", signed=False)
                bytes_data = bytes_data + value

    return bytes_data
  • この関数を使って元に戻すことができる。
vals = [123.4, 234.5, 345.6, 456.7]
bytes_data1 = b''.join([struct.pack('<f', v) for v in vals])
bytes_data1
# Out: b'\xcd\xcc\xf6B\x00\x80jC\xcd\xcc\xacC\x9aY\xe4C'

str_data = str(bytes_data1)
str_data
# Out: "b'\\xcd\\xcc\\xf6B\\x00\\x80jC\\xcd\\xcc\\xacC\\x9aY\\xe4C'"

bytes_data2 = str_to_bytes(str_data)
bytes_data2
# Out: b'\xcd\xcc\xf6B\x00\x80jC\xcd\xcc\xacC\x9aY\xe4C'
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2