Pythonのプリプロセッサ
(pydong.org)Pythonのプリプロセッサ
- Pythonにプリプロセッサがないという主張は事実ではない
- Pythonには非常に強力なプリプロセッサがある
Pythonソースコードのエンコーディング
- PEP-0263 によりソースコードのエンコーディングを定義できる
- 先頭2行にマジックコメントを追加してエンコーディングを設定できる
- 例:
# coding=utf8,# -*- coding: utf8 -*-,# vim: set fileencoding=utf8 :
パス設定ファイル(.pth)
- Pythonインタプリタが
-Sオプションなしで起動すると、siteパッケージが自動的に読み込まれる site-packagesフォルダに.pthファイルを追加してモジュール探索パスを拡張できる.pthファイル内でimportで始まる行は実行される- これにより、Pythonインタプリタの初期化時に任意のコードを実行できる
カスタムコーデックの定義
- Pythonインタプリタが満たすべき要件は2つある:
decode(data: bytes) -> tuple[str, int]関数- インクリメンタルデコーダクラス
codecs.utf_8_decodeを使って実際のデコードを行い、結果の文字列をプリプロセッサに渡す- 例外を捕捉して出力し、再送出するのが望ましい
インクリメンタルデコーダの提供
codecs.BufferedIncrementalDecoderを継承してインクリメンタルデコーダを実装する- バッファにデータを収集し、最終デコード呼び出し時にファイル全体を前処理する
Pythonの拡張
- Pythonの標準ライブラリを使ってPythonを拡張するのは比較的容易
tokenizeモジュールでファイルのトークンストリームを変更したり、astモジュールで抽象構文木を変更したりできる
単項インクリメントとデクリメント
- Pythonには単項インクリメント演算子とデクリメント演算子がない
x++,x--は無効++x,--xは有効だが、別の意味を持つ- 単項インクリメント/デクリメント式をPythonの式に変換できる
例
- 入力ファイル
incdec.py:# coding: magic.incdec i = 6 assert i-- == 6 assert i == 5 assert ++i == 6 assert --i == 5 assert i++ == 5 assert i == 6 assert (++i, 'i++') == (7, 'i++') print("PASSED") - 変換後のファイル:
i = 6 assert ((i, i := i - 1)[0]) == 6 assert i == 5 assert ((i, i := i + 1)[1]) == 6 assert ((i, i := i - 1)[1]) == 5 assert ((i, i := i + 1)[0]) == 5 assert i == 6 assert (((i, i := i + 1)[1]), 'i++') == (7, 'i++') print("PASSED")
波かっこを使うPython(Bython)
- Pythonのインデントの代わりに波かっこでスコープを指定できる
tokenize.generate_tokensを使ってトークンストリームを変更する
例
- 入力ファイル
test.by:# coding: magic.braces def print_message(num_of_times) { for i in range(num_of_times) { print("braces ftw") } print({'x': 3}) } x = { 'foo': 42, 'bar': 5 } if __name__ == "__main__" { print_message(2) print({k: v for k, v in x.items()}) } - 変換後のファイル:
def print_message(num_of_times): for i in range(num_of_times): print("braces ftw") print({'x': 3}) x = { 'foo': 42, 'bar': 5 } if __name__ == "__main__": print_message(2) print({k: v for k, v in x.items()})
他言語の解釈
- Pythonインタプリタに他の言語を解釈させることができる
- 例: C, C++, TOML
例
- C++ファイル
test.cpp:#define CODEC "coding:magic.cpp" #include <cstdio> int main() { puts("Hello World"); } - 変換後のファイル:
import cppyy cppyy.cppdef(r""" #define CODEC "coding:magic.cpp" #include <cstdio> int main() { puts("Hello World"); } """) from cppyy.gbl import main if __name__ == "__main__": main()
データ検証
- TOML形式のデータをJSON Schemaで検証できる
jsonschemaを使って実際の検証を行う
例
- スキーマファイル
schema.json:{ "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "number"}, "scores": { "type": "array", "items": {"type": "number"} }, "address": {"$ref": "#/$defs/address"} }, "required": ["name"], "$defs": { "address": { "type": "object", "properties": { "street": {"type": "string"}, "postcode": {"type": "number"} }, "required": ["street"] } } } - 有効なデータファイル
data_valid.toml:# coding: magic.toml name = "John Doe" age = 42 scores = [40, 20, 80, 90] [address] street = "Grove St. 4" postcode = 19201 - 無効なデータファイル
data_invalid.toml:# coding: magic.toml name = "John Doe" age = 42 scores = [40, "20", 80, 90] [address] street = "Grove St. 4" postcode = 19201
結論
- カスタムコーデックとパス設定ファイルを使うことで、Pythonインタプリタの動作を大きく変更できる
- 例としては
pythonql,future-typing,future-fstrings,future-annotationsなどがある magic_codecを使えば、自分自身のプリプロセッサを簡単に試せる
GN⁺の要約
- Pythonのプリプロセッサを活用して、さまざまな言語拡張やデータ検証を行える
- カスタムコーデックを通じてPythonインタプリタの動作を変更する方法を説明している
- この記事はPython開発者にとって有用なツールと手法を提供する
- 類似の機能を持つプロジェクトとして
pythonql,future-typingなどがある
1件のコメント
Hacker Newsのコメント
from __future__ import bracesの構文エラーメッセージは、2001年から cpython にハードコードされているimport-hooks を使った創造的な解雇方法を考えたが、codec regex が
"μtf8"のようなものを使えないようにしていて残念python がプリプロセッサフックを公開していないのには理由があり、分別のある大人なら避けるべきだと思う
プリプロセッサのほうがより便利で有用だ
python の柔軟性が大好きだ
pyxl を使った最高の事例は jsx に触発されたもの
# coding: pyxlを使って HTML コードを書けるPython 2 から 3 への移行をもっとうまく処理できたのではないかと思う
# coding: six.python2は Python2 のコードを Python3 で有効にし、# coding: six.python3は Python3 のコードを Python2 で実行可能なように調整できるこのアイデアを気に入ってくれてうれしい。近いうちにさらに多くの内容を出す予定
久しぶりに、まったく新しいアイデアに驚かされた
Python でインラインコード生成をしたいなら、Ned Batchelder の cog を使える
この coding hook 戦略で導入された依存関係が
pip freezeや uv によって検出されるのか気になる