Pythonの文法エラー箇所(行番号、何文字目)を取得するには
Pycon JPでPythonをサポートしてないIDEを紹介したトークがあったらしい()
さて、競プロIDEのPython対応を進めていますが、Pythonのシンタックスエラー箇所取得に少しつまづいたのでメモ。 IDEで文法エラー箇所をハイライトするには何行目、何文字目で文法エラーが起きているかを調べる必要があります。
結論
動的インポートしてシンタックスエラーをキャッチしてやればよい。
import importlib.machinery loader = importlib.machinery.SourceFileLoader("<py_compile>", "test.py") source_bytes = loader.get_data("test.py") try: code = loader.source_to_code(source_bytes, "test.py") except SyntaxError as e: print(":".join(("error", str(e.lineno), str(e.offset))))
過程
最初はpython -m py_compile test.py
の出力をパースしてエラー箇所(行数と何文字目か)を取得しようとしました。
例えば
for i i range(3): # typo: i in range(3)が正解 print(i)
みたいなコードに対してpy_compileをかけたとします。するとその出力は
$ python3 -m py_compile ./sa.py File "./test.py", line 1 for i i range(3): ^ SyntaxError: invalid syntax
となります。 ユーザーフレンドリーな出力ではありますが、エラー場所をint型で得るには行数はともかく、何文字目かを取得するのはちょっとめんどいです。 ^記号の前に何個スペースがあるかを数える方法もありますが、美しくはないですし、仕様でもなさそうです。
ところで、この出力をどうやってPythonが出しているかを調べると、結局SyntaxErrorの仕様を調べるとlinenoとoffsetというメンバーを持っており、 これが所望のデータであることがわかりました。
なのでSyntaxErrorをキャッチしてこれらメンバにアクセスすればいいですが、アプリケーションの例外ではなく読み出しているコードについて例外を出してやらなければいけません。
IDEの中でimport py_compile
して、例外をキャッチするというのもやってみましたが、SyntaxErrorは内部でキャッチされてしまい、SyntaxErrorを出してやることができませんでした。
そこで次に、py_compileモジュールがどのようにエラーを出力しているか実装を読んでみると、冒頭のように動的importしてSyntaxErrorを出力させていたことがわかりました。
感想
いいノウハウなので、Pyconに間に合わせればよかった。