Pythonで簡単な構文解析器を実装する方法(pyparsing の使い方)

Python構文解析を行う方法です.

BNFを書いてがっつり実装する場合は Lark などを使いますが,このエントリでは簡便な方法として pyparsing モジュールの使い方を紹介します.

ログファイルから特定のデータだけを抜き出す,といった用途ならpyparsingで十分です.

pyparsing の使い方

サンプル

とりあえず以下のログファイルを読み込むと

code=200 msg=OK
code=401 msg=NotFound
Error: no such file.

以下の情報を取り出すコードを目標とします

  • (200, "OK")
  • (401, "NotFound")


が,話を簡単にするために,ひとまず
”code=200 msg=OK”から(200,"OK")を取り出すコードを書くと
次のようになります

import pyparsing as pp
from pyparsing import pyparsing_unicode as uni

token_string = pp.Combine(pp.Word(uni.printables), adjacent=False, joinString=" ")
token_int = pp.Word(pp.nums)
token_float = pp.Combine(pp.Optional('-') + pp.Word(pp.nums) + '.' + pp.Word(pp.nums))
syntax = "code=" + token_int("code_number") + "msg=" + token_string("msg_text")

result = syntax.search_string("code=200 msg=OK")

for e in result:
    print(list(e.items()))

実行すると

[('code_number', '200'), ('msg_text', 'OK')]

という結果になります

説明

モジュールの読み込み

import pyparsing as pp
from pyparsing import pyparsing_unicode as uni

pyparsing_unicode は日本語(ユニコード文字列)に対応するために必要となるモジュールです

トークンの定義

データに対応する文字列をトークンとして定義します

  • 文字列のデータに対して token_sring
  • 整数のデータに対して token_int
  • 浮動小数点のデータに対して token_float

を定義しています

token_string = pp.Combine(pp.Word(uni.printables), adjacent=False, joinString=" ")
token_int = pp.Word(pp.nums)
token_float = pp.Combine(pp.Optional('-') + pp.Word(pp.nums) + '.' + pp.Word(pp.nums))
  • pp.Word(uni.printables) は,空白以外の文字(ユニコードも含む)の1っ回以上の繰り返し
  • pp.Word(pp.nums)は,正義表現の [0-9]+ に対応します

式の定義

抽出したい行は

code=整数値 msg=文字列

の形になります

また読み取った整数値には code_number,
文字列には msg_text という変数名をつけることにします

これをpythonでかくと

syntax = "code=" + token_int("code_number") + "msg=" + token_string("msg_text")

となります

構文解析の実行

result = syntax.search_string("code=200 msg=OK"")

for e in result:
    print(list(e.items()))

実行結果は

[('code_number', '200'), ('msg_text', 'OK')]

となり,200とOKが抽出できていることがわかります

最終的なコード

import pyparsing as pp
from pyparsing import pyparsing_unicode as uni

token_string = pp.Combine(pp.Word(uni.printables), adjacent=False, joinString=" ")
token_int = pp.Word(pp.nums)
token_float = pp.Combine(pp.Optional('-') + pp.Word(pp.nums) + '.' + pp.Word(pp.nums))

syntax = "code=" + token_int("code_number") + "msg=" + token_string("msg_text")

data = """
code=200 msg=OK
code=401 msg=NotFound
Error: no such file.
"""

for text in data.split("\n"): 
    result = syntax.search_string(text)
    for e in result:
        print(list(e.items()))

結果は

[('code_number', '200'), ('msg_text', 'OK')]
[('code_number', '401'), ('msg_text', 'NotFound')]

データが多い時はpandasのDataFrameに突っ込むと使い勝手がいいです

import pandas as pd

tmp = []
for text in data.split("\n"): 
    result = syntax.search_string(text)
    for e in result:
        tmp.append(e.items())
df = pd.DataFrame(tmp)

display(df)

これで結果を表形式で表示できます