pythonの数値・bytes相互変換（＋おまけ：bytesを誤ってstr変換して保存してしまった場合）


## はじめに

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

## import
```python
import struct
```

## float ⇔ bytes

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

- float ⇒ bytes

```python
val = 123.4
struct.pack('<f', val)
# Out: b'\xcd\xcc\xf6B'
```

- bytes ⇒ float

```python
struct.unpack('<f', b'\xcd\xcc\xf6B')[0] # tuple型になるので0番目を取り出す。
# Out: 123.4000015258789
```

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

`"<ff"`と必要な要素数分`f`を繰り返し書けば変換できる。
 
- float配列 ⇒ bytes

```python
vals = [123.4, 234.5]
struct.pack('<ff', *vals)
# Out: b'\xcd\xcc\xf6B\x00\x80jC'
```

- bytes ⇒ float配列

```python
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を組み合わせる。

```python
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内包表記と組み合わせる。

```python
[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にしてファイル出力してしまった場合に元に戻したい。

```python
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'" # これでファイルに書き込んでしまった！！汗
```

- 以下のような関数で逆変換ができる。
  - 実行方法はコード内コメントを参照。

```python
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
```

- この関数を使って元に戻すことができる。

```python
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'
```