僕は英語が苦手です。
中学レベルの英語すら読み書きできないのにイキって英単語を並べるものですから、関数名や変数名に目も当てられないような命名をしてしまうことがままあります。
最近に関しては、ユニットテストのテスト関数名を日本語で書く始末(def test_必須項目が空の場合エラーになること(self):
)
そこで僕は思いました、日本語でPythonを書けないかと・・・。
そして気づきました、
「PythonはOSSだから改造できるじゃないか...!!!」
~~それから僕の辛く長いPython改造の日々が始まったのです。~~当方の環境
Mac OS BigSur 11.2.1
pyenv 1.2.22
Pythonのソースコードをダウンロード
https://www.python.org/downloads/source/
tarball形式のものをダウンロードします。
今回はv3.9.2をダウンロードしました。
ダウンロードしたファイルを展開&インストール
pyenvで使用したいので、ビルドしたPythonのコードは「~/.pyenv/versions/jpython」に出力されるようにします。
日本語のpythonなので「jpython」としました(安直)
$ cd /Users/~~/download
# 展開
# ダウンロードフォルダ直下で作業しているので、それが嫌な人はダウンロードしたファイルをどこに移動させてください。
$ tar zxvf Python-3.9.2.tgz
$ cd Python-3.9.2
# makefile作成
# -gはデバッグシンボルを付けるオプション。デバッガを使用するならつけた方が良いらしい。
# -O0は最適化を行わないオプション。デバッグ時にソースコードがそのままの状態で読めるらしい
# --prefix インストール先のディレクトリ。make install実行時、このオプションに指定したディレクトリに出力されます。
$ CFLAGS="-g -O0" ./configure --prefix=/Users/~~/.pyenv/versions/jpython
# インストール
# -jは同時に実行できるプロセス(ジョブ?)の数を指定できる(コンパイル時間の短縮)
# $(nproc)は利用可能なプロセス数を返すコマンド
$ make -j$(nproc)
$ make install
インストールしたPythonを実行
$ ~/.pyenv/versions/jpython/bin/python3
Python 3.9.2 (default, Mar 20 2021, 16:36:40)
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def こんにちはと言う():
... print('こんにちは')
...
>>> こんにちはと言う()
こんにちは
>>>
インストールしたPythonが実行できることが確認できました。
インストールできたら、pyenv localを実行して適用しておきます。
# ~/.pyenv/versions/jpython に出力したので、pyenv local(またはglobal)で指定できる
$ pyenv local jpython
日本語化開始
手始めに「def」の代わりとなる「関数」を定義したい
まずはドキュメントを読みます。
ふむふむ、Pythonの文法はGrammar/python.gram
に書いてあるそうな。
早速見てみると、それらしい部分が見つかりました
.
..
...
compound_stmt[stmt_ty]:
| &('def' | '@' | ASYNC) function_def ←←←←←←←← コレ
| &'if' if_stmt
| &('class' | '@') class_def
| &('with' | ASYNC) with_stmt
| &('for' | ASYNC) for_stmt
| &'try' try_stmt
| &'while' while_stmt
...
..
.
# ↓↓↓↓↓↓↓↓↓↓↓↓↓ コレ
function_def[stmt_ty]:
| d=decorators f=function_def_raw { _PyPegen_function_def_decorators(p, d, f) }
| function_def_raw
# ↓↓↓↓↓↓↓↓↓↓↓↓↓ コレ
function_def_raw[stmt_ty]:
| 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
_Py_FunctionDef(n->v.Name.id,
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA) }
| ASYNC 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
CHECK_VERSION(
5,
"Async functions are",
_Py_AsyncFunctionDef(n->v.Name.id,
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA)
) }
ここに"def"に関するものをコピペして"関数"に書き換えます
必ずダブルクォートを使用してください。
.
..
...
compound_stmt[stmt_ty]:
| &('def' | '@' | ASYNC) function_def
| &("関数" | '@' | ASYNC) jpdef ←←←←←←←← 追加
| &'if' if_stmt
| &('class' | '@') class_def
| &('with' | ASYNC) with_stmt
| &('for' | ASYNC) for_stmt
| &'try' try_stmt
| &'while' while_stmt
...
..
.
# ↓↓↓↓↓↓↓↓↓↓↓↓↓ 追加
function_jpdef[stmt_ty]:
| d=decorators f=function_jpdef_raw { _PyPegen_function_def_decorators(p, d, f) }
| function_jpdef_raw
# ↓↓↓↓↓↓↓↓↓↓↓↓↓ 追加
function_jpdef_raw[stmt_ty]:
| "関数" n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
_Py_FunctionDef(n->v.Name.id,
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA) }
| ASYNC "関数" n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
CHECK_VERSION(
5,
"Async functions are",
_Py_AsyncFunctionDef(n->v.Name.id,
(params) ? params : CHECK(_PyPegen_empty_arguments(p)),
b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA)
) }
ドキュメントによると、python.gram変更後はmake regen-pegen
を実行してください、と書いてあります。
ただ、このままだとmake regen-pegen
は失敗してしまうので、実行時に使用されるスクリプトを少し書き換えます。
.
..
...
def visit_StringLeaf(self, node: StringLeaf) -> FunctionCall:
val = ast.literal_eval(node.value)
if re.match(r"[a-zA-Z_]\w*\Z", val) or val in ['関数']: # ←←←←←←←← or val in ['関数'] を追加
if node.value.endswith("'"):
return self.keyword_helper(val)
else:
return self.soft_keyword_helper(node.value)
...
..
.
書き換えが完了したらmake regen-pegen
を実行します。
% make regen-pegen
PYTHONPATH=./Tools/peg_generator python3.9 -m pegen -q c \
./Grammar/python.gram \
./Grammar/Tokens \
-o ./Parser/pegen/parse.new.c
python3.9 ./Tools/scripts/update_file.py ./Parser/pegen/parse.c ./Parser/pegen/parse.new.c
parse.cとparse.new.cが生成されたようです。
parse.cの中身を確認してみると、python.gramに追加した関数が定義されてあります、成功です。
.
..
...
// _tmp_16: "関数" | '@' | ASYNC
static void *
_tmp_16_rule(Parser *p)
{
D(p->level++);
if (p->error_indicator) {
D(p->level--);
return NULL;
}
void * _res = NULL;
int _mark = p->mark;
{ // "関数"
if (p->error_indicator) {
D(p->level--);
return NULL;
}
D(fprintf(stderr, "%*c> _tmp_16[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"関数\""));
expr_ty _keyword;
if (
(_keyword = _PyPegen_expect_soft_keyword(p, "関数")) // soft_keyword='"関数"'
)
{
D(fprintf(stderr, "%*c+ _tmp_16[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"関数\""));
_res = _keyword;
goto done;
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s _tmp_16[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"関数\""));
}
...
..
.
}
それではビルドしてみましょう。
# make installの前にcleanを実行するようにしましょう
# ドキュメントにも「うまく行かない場合は make clean を実行しましょう」と書いてありますので、事前に実行しときます
$ make clean
# エラーで失敗しなければOKです
$ make install
...
..
.
Looking in links: /var/folders/jk/884fgr8j3nx2dsh0k92f7yc00000gp/T/tmpzs0_oh0d
Requirement already up-to-date: setuptools in /Users/~~/.pyenv/versions/jpython/lib/python3.9/site-packages (49.2.1)
Requirement already up-to-date: pip in /Users/~~/.pyenv/versions/jpython/lib/python3.9/site-packages (20.2.3)
$
さっそく関数を使ってみます。
関数 こんにちはという():
print('こんにちは')
こんにちはという()
python3 test.py
こんにちは
無事、"def"を"関数"に置き換えることができました。
class...?否、"クラス"でしょ
defを置き換えた時と同じ要領で進めます。
・Grammar/python.gram書き換え
・Tools/peg_generator/pegen/c_generator.py書き換え
・make regen-pegen実行
・make clean実行
・make install実行
クラス 人間:
関数 こんにちはという(自分自身):
print('こんにちは')
人間インスタンス = 人間()
人間インスタンス.こんにちはという()
$ python3 test.py
こんにちは
if:-else:...?否、"もし:-それ以外:"でしょ(elifは日本語にどう起こせばいいかわからなったので無視しました(照))
数字1 = 10
数字2 = 20
数字3 = 30
もし 数字2 > 数字1:
print(f'{数字1}より{数字2}のほうが大きい')
もし 数字2 > 数字3:
print(f'{数字3}より{数字2}のほうが大きい')
それ以外:
print(f'{数字2}より{数字3}のほうが大きい')
$ python3 test.py
10より20のほうが大きい
20より30のほうが大きい
日本語化した文法まとめ
コンソール出力 = print
クラス キャラ:
タイプ = ''
関数 僕は何者(自分):
コンソール出力(f'僕は{自分.タイプ}です')
クラス 陽キャ(キャラ):
タイプ = '陽キャ'
クラス 無キャ(キャラ):
タイプ = '無キャ'
陽キャ君 = 陽キャ()
無キャ君 = 無キャ()
陽キャ君.僕は何者() # >>> 僕は陽キャです
無キャ君.僕は何者() # >>> 僕は無キャです
もし 無キャ君.タイプ == '陰キャ':
コンソール出力('どぅふ')
それ以外:
コンソール出力('寝る')
どうでしょう、この脳内にダイレクトに語りかけてくるようなソースコードは。
もう英単語で悩まなくていいのです。
最後に
メリット
・特になし。仮に日本語でソースかけたとしても、読みづらくなっていく気しかしない
デメリット
・全角と半角の切り替えめんどくさい
・環境によって文字化けしそう
・Pythonの改造に時間がかかる
・変数名や関数名を日本語にしたいだけなら別に普通のPythonでもできる
・他にもたくさん
なにより、凝ったことしようとすると、結局英語のドキュメント読まされる羽目になる
感想
Pythonを改造してみたくなって出来心でやってしまいました。
実際の開発では全く役に立たないことは承知の上です ので叩かないでください
本当はコンストラクタやpassなども日本語化したかったのですが、ソース追うのがめんどくさすぎて辞めました・・・C言語が読めればとっつきやすいのだろうか??
これからも拙い英語でプログラム書いていきます・・・泣
参考
make -jの値には何を指定するべき?それと注意事項。
24. Changing CPython’s Grammar
Pythonを改造してみた はじめに