verilog書く人

自称ASIC設計者です。どなたかkaggle一緒に出ましょう。

Pythonの文法エラー箇所(行番号、何文字目)を取得するには

Pycon JPでPythonをサポートしてないIDEを紹介したトークがあったらしい()

さて、競プロIDEPython対応を進めていますが、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に間に合わせればよかった。