Python matchステートメントとバイトコード
Google検索が劣化し、次の検索結果を表示しています
を抑制するnfpr=1
や
含まれない
を無効化するっぽいnirf=1
を駆使してもワード分割が酷くなり
※Googleまでもがクォートを案内するが、完全一致は複数形やスペースなど表現の揺らぎさえ消える
結果をまとめサイトが跋扈する昨今、別コミュニティでノウハウを残したい
という事でネタは色々あるのだけど、久しぶりにPythonコードを書いていて
あー任天堂switch
したいと思いきや、いつの間にか実装されてたので検証
# coding=mbcs from __future__ import absolute_import, division, print_function import dis def pattern(var1): match var1: case 100: print("百") case 1000: print("千") case _: print("その他") dis.dis(pattern)
しかし1行目はダメな例として書いた。coding=mbcs
は通るがWindows専用で
システムロケール(CP_ACP)を指すため、別環境ではスクリプトと整合性を失う
実行環境に即したファイル読み書きなどを行うときに使うエンコーディングだ
utf-8
と指定すべき所、ダメ文字を抱えていてもcp932
が便利な場合も有る
2行目はmatch
文を使う以上関係のない、言わずもがな2系で動かすお呪いだが
unicode_literals
は非推奨という点で記述した、代表的にはr"\u"
が転ける
RAW文字列リテラルはunicode
になると2系はUnicodeエスケープが有効になり
3系はu
も付くと忽ち文法エラーになるが、ファイルパス等にbr
は宜しくない
またsys.stdout
がascii
な場合とかを筆頭に2系はbytes
ファーストなので
unicode_literals
はimport
せず、適宜u
やb
を接頭しておくのが良さげ?
バイトコード
閑話休題、match
文を知るには言語リファレンスを読むのが正攻法ではあるが
実際どういう動きをするかは個人的にバイトコードを調べるのが手っ取り早い
6 0 LOAD_FAST 0 (var1) # var1 7 2 DUP_TOP # var1 var1 4 LOAD_CONST 1 (100) # var1 var1 100 6 COMPARE_OP 2 (==) # var1 var1==100 8 POP_JUMP_IF_FALSE 12 (to 24) # var1 10 POP_TOP 8 12 LOAD_GLOBAL 0 (print) # print 14 LOAD_CONST 2 ('百') # print '百' 16 CALL_FUNCTION 1 # None 18 POP_TOP 20 LOAD_CONST 0 (None) # None 22 RETURN_VALUE 9 >> 24 LOAD_CONST 3 (1000) # var1 1000 26 COMPARE_OP 2 (==) # var1==1000 28 POP_JUMP_IF_FALSE 21 (to 42) 10 30 LOAD_GLOBAL 0 (print) # print 32 LOAD_CONST 4 ('千') # print '千' 34 CALL_FUNCTION 1 # None 36 POP_TOP 38 LOAD_CONST 0 (None) # None 40 RETURN_VALUE 11 >> 42 NOP 12 44 LOAD_GLOBAL 0 (print) # print 46 LOAD_CONST 5 ('その他') # print 'その他' 48 CALL_FUNCTION 1 # None 50 POP_TOP 52 LOAD_CONST 0 (None) # None 54 RETURN_VALUE
コメントは筆者によるスタックの変遷を示す。おわかりいただけるだろうか?
スタックの複製を除けば、if
-elif
-else
文とほとんど変わらないのである
6 0 LOAD_FAST 0 (var1) 2 LOAD_CONST 1 (100) 4 COMPARE_OP 2 (==) 6 POP_JUMP_IF_FALSE 10 (to 20) 7 8 LOAD_GLOBAL 0 (print) 10 LOAD_CONST 2 ('百') 12 CALL_FUNCTION 1 14 POP_TOP 16 LOAD_CONST 0 (None) 18 RETURN_VALUE 8 >> 20 LOAD_FAST 0 (var1) 22 LOAD_CONST 3 (1000) 24 COMPARE_OP 2 (==) 26 POP_JUMP_IF_FALSE 20 (to 40) 9 28 LOAD_GLOBAL 0 (print) 30 LOAD_CONST 4 ('千') 32 CALL_FUNCTION 1 34 POP_TOP 36 LOAD_CONST 0 (None) 38 RETURN_VALUE 11 >> 40 LOAD_GLOBAL 0 (print) 42 LOAD_CONST 5 ('その他') 44 CALL_FUNCTION 1 46 POP_TOP 48 LOAD_CONST 0 (None) 50 RETURN_VALUE
LOAD_FAST
がDUP_TOP
に成って得られるパフォーマンスは乏しいだろう
しかしmatch
文はswitch
文に非ず、構造的パターンマッチであるという
何れにも該当しなかった節がdefault
やelse
でないのもソレ故とScalaか
シーケンスパターン
立ち返って言語リファレンスを見やる、そしてサンプルを逆アッセンボー
6 0 LOAD_CONST 1 ((100, 200)) 7 2 DUP_TOP 4 MATCH_SEQUENCE 6 POP_JUMP_IF_FALSE 22 (to 44) 8 GET_LEN 10 LOAD_CONST 2 (2) 12 COMPARE_OP 2 (==) 14 POP_JUMP_IF_FALSE 22 (to 44) 16 UNPACK_SEQUENCE 2 18 LOAD_CONST 3 (100) 20 COMPARE_OP 2 (==) 22 POP_JUMP_IF_FALSE 22 (to 44) 24 LOAD_CONST 4 (300) 26 COMPARE_OP 2 (==) 28 POP_JUMP_IF_FALSE 23 (to 46) 30 POP_TOP 8 32 LOAD_GLOBAL 0 (print) 34 LOAD_CONST 5 ('Case 1') 36 CALL_FUNCTION 1 38 POP_TOP 40 LOAD_CONST 0 (None) 42 RETURN_VALUE 7 >> 44 POP_TOP 9 >> 46 DUP_TOP 48 MATCH_SEQUENCE 50 POP_JUMP_IF_FALSE 46 (to 92) 52 GET_LEN 54 LOAD_CONST 2 (2) 56 COMPARE_OP 2 (==) 58 POP_JUMP_IF_FALSE 46 (to 92) 60 UNPACK_SEQUENCE 2 62 LOAD_CONST 3 (100) 64 COMPARE_OP 2 (==) 66 POP_JUMP_IF_FALSE 46 (to 92) 68 LOAD_CONST 6 (200) 70 COMPARE_OP 2 (==) 72 POP_JUMP_IF_FALSE 47 (to 94) 74 LOAD_FAST 0 (flag) 76 POP_JUMP_IF_FALSE 47 (to 94) 78 POP_TOP 10 80 LOAD_GLOBAL 0 (print) 82 LOAD_CONST 7 ('Case 2') 84 CALL_FUNCTION 1 86 POP_TOP 88 LOAD_CONST 0 (None) 90 RETURN_VALUE 9 >> 92 POP_TOP 11 >> 94 MATCH_SEQUENCE 96 POP_JUMP_IF_FALSE 67 (to 134) 98 GET_LEN 100 LOAD_CONST 2 (2) 102 COMPARE_OP 2 (==) 104 POP_JUMP_IF_FALSE 67 (to 134) 106 UNPACK_SEQUENCE 2 108 LOAD_CONST 3 (100) 110 COMPARE_OP 2 (==) 112 POP_JUMP_IF_FALSE 67 (to 134) 114 STORE_FAST 1 (y) # <- ! 12 116 LOAD_GLOBAL 0 (print) 118 LOAD_CONST 8 ('Case 3, y: ') 120 LOAD_FAST 1 (y) 122 FORMAT_VALUE 0 124 BUILD_STRING 2 126 CALL_FUNCTION 1 128 POP_TOP 130 LOAD_CONST 0 (None) 132 RETURN_VALUE 11 >> 134 POP_TOP 13 136 NOP 14 138 LOAD_GLOBAL 0 (print) 140 LOAD_CONST 9 ('Case 4, I match anything!') 142 CALL_FUNCTION 1 144 POP_TOP 146 LOAD_CONST 0 (None) 148 RETURN_VALUE
おっと…見慣れない命令MATCH_SEQUENCE
の出現だ、がドキュメント曰く
If TOS is an instance of
collections.abc.Sequence
and is not an instance ofstr
/bytes
/bytearray
(or, more technically: if it has thePy_TPFLAGS_SEQUENCE
flag set in itstp_flags
),
pushTrue
onto the stack. Otherwise, pushFalse
.
これは後述する特殊化を含めた判定に過ぎず、それ以降は要素毎の比較だ
とすれば真価の発揮は専用命令があるMATCH_KEYS
やMATCH_CLASS
なのか
しかし今回それは割愛、先の特殊化判定。私は行内容の判別を書いていた
それでmatch
文に巧く書き直せないかと思ったのだが…リファレンス曰く
If the subject value is an instance of
str
,bytes
orbytearray
the sequence pattern fails.These classes accept a single positional argument,
and the pattern there is matched against the whole object rather than an attribute.
つまりbytearray
でさえシーケンス扱いには成らず、部分一致も不可
memoryview
やアンパックすれば可能だが、そんな事よりin
演算子か
位置で決まればスライス、又はstartswith()
やfind()
した方が良い
split()
で扱える場合でも、行頭判別で特定行のみ処理なら非効率だ
論拠でErlangへの言及はある、バイナリパターンマッチもしたいです
そんな訳で私のユースケースにインパクトを与える代物ではなかった
言語リファレンス
とはいえ冗長な記述を推敲できる糖衣構文だ、但し要注意なのが文法
match
に与えるsubject_expr
はstar_named_expression
等とされ
これは式のリストに近しく見えるが、case
節へ書くのはpatterns
だ
間違っても式expression
ではなく、switch
やRubyのwhen
節と違う
言語リファレンスのサンプルを逆アセンブルした先の結果も示すとおり
コード中に出てくるcase
節のy
へはSTORE_FAST
、つまり代入している
ここへ書いた変数は参照元ではなく代入先で、capture_pattern
になる
関数オーバーロードにおける仮引数と考えればシグネチャの様な感じか
参照元value_pattern
となるケースは、メンバー参照のみとなっている
またNone
、True
、False
も特殊でliteral_pattern
に分類されるが
この定数というかシングルトン、キーワードにはis
演算子が使われる
match True: case 1 as x: print(x)
1 0 LOAD_CONST 0 (True) 2 2 DUP_TOP 4 LOAD_CONST 1 (1) 6 COMPARE_OP 2 (==) 8 POP_JUMP_IF_FALSE 11 (to 22) 10 STORE_NAME 0 (x) 3 12 LOAD_NAME 1 (print) 14 LOAD_NAME 0 (x) 16 CALL_FUNCTION 1 18 POP_TOP 20 JUMP_FORWARD 1 (to 24) 2 >> 22 POP_TOP
という場合にはマッチしても、下記だとマッチしないという仕様である
match 1: case True as x: print(x)
1 0 LOAD_CONST 0 (1) 2 2 DUP_TOP 4 LOAD_CONST 1 (True) 6 IS_OP 0 8 POP_JUMP_IF_FALSE 11 (to 22) 10 STORE_NAME 0 (x) 3 12 LOAD_NAME 1 (print) 14 LOAD_NAME 0 (x) 16 CALL_FUNCTION 1 18 POP_TOP 20 JUMP_FORWARD 1 (to 24) 2 >> 22 POP_TOP
流れ落ちずbreak
もなくor_pattern
を書くが、これも|
演算子ではない
guard
の後置if
は内包表記でas_pattern
はwith
やexcept
でお馴染み
wildcard_pattern
はcapture_pattern
の破棄するバージョンなだけだ
所懐
switch
したいと思って出逢ったmatch
だったが、ちょっと複雑だった
Elixirのパターンマッチングも鑑みれば、些かPythonicさに欠けるか?
これを否定する気は無いが、3.10
~という新しさは時間が解決しても
Windowsだと7が期限切れとはいえ、3.9
以降をインストールできない
api-ms-win-core-path-l1-1-0.dll
の関数参照は数個で代替可能…
10
へと半ば強制更新したMicrosoftの暴挙は環境均一化に寄与したが
AF_UNIX
を実装してもSOCK_DGRAM
が欠けるからかPythonは未対応だ
Python3の肝はやはりasyncio
だと思うが、これもかなり制限がある
セイウチ演算子が導入され、7でも動く3.8
は分水嶺的な魅力があり
徒にmatch
文を使うのは躊躇われる。だがエラー改良は素晴らしい!!