🐍

Python3.10のパターンマッチングで、バイナリプロトコルをパースしてみる

1 min read

Python3.10a6 で待望の?パターンマッチング PEP 622 の実装が入ったので、早速バイナリプロトコルのパースを試してみました。

サンプルとして、データ長(2byte) コマンド種別(1byte) データ(任意) Checksum(1byte) みたいなプロトコルを考えます。

 0                   1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Data Length          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Command    |               |
+---------------+               +
|                               |
+              Data             +
|                               |
+               +-+-+-+-+-+-+-+-+
|               |    Checksum   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

これに対するハンドラは以下のように書けるでしょうか。

_data = b'Hello World!'
_command = b'\x01'
_len = len(_data).to_bytes(2, byteorder='big')
_checksum = b'\xae'  # TODO
msg = _len + _command + _data + _checksum

# validator
match memoryview(msg):
    case [len1, len2, _, *data, csum] if len(data) == (len1 << 8) + len2 \
            and csum == 0xae:  # TODO
        print('valid message')
    case _:
        print('invalid message')

# handler
match memoryview(msg):
    case [_, _, 0x01, *data, _]:
        print('command1', bytes(data))
    case [_, _, 0x02, *data, _]:
        print('command2', bytes(data))
    case _:
        print('unknown command')

memoryview() でラップしているのは、
https://www.python.org/dev/peps/pep-0622/#sequence-patterns: "it cannot be any kind of string (str, bytes, bytearray)."
ということで bytes を直接使えないからです。

if で数珠つなぎにするよりもコードの意図を明確かつ短く書けそうです。
正式リリース(とエディタの対応)が待ち遠しいですね!

case [length[2], _, *data, csum] みたく長さも指定できるとかっこいいようにも思いますが、どうなんでしょうね。

Discussion

ログインするとコメントできます