Pythonを魔改造して、できるだけ日本語で卵焼き作ってみた

2021.03.30

2021.08.12

Fabeee社員ブログ


こんにちはバターです。
Pythonで日本語が扱えるように魔改造して卵焼きを作ってみました。
恐れ多くもソースコードは弊社CTO 兼 取締役が書いたコードをもろパクリしましたm(_ _)m。
Pythonで卵焼き作ってみた【Python】【入門】
素のPythonでも日本語で変数を用意したり関数を用意したりできますが、それだけでは飽き足らず、他の文法もできるだけ日本語で記述できるようにしてみます。

 

Python魔改造準備

ソースコードをダウンロード
Pythonを改造するには、Pythonのソースコードが必要です、ダウンロードしましょう。
(tarball形式のものをダウンロード)
ダウンロードしたファイルを展開&インストール

$ 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 hello_world():
...     print('Hello World')
...
>>> hello_world()
Hello World
>>>

無事、実行できたようなので改造に入ります。

Python魔改造

初めに”def”の代わりになる”関数”を定義する
まずは公式ドキュメントを読みます。
ふむふむ、Pythonの文法はGrammar/python.gramに書いてあるそうな。
早速見てみると、それらしい部分が見つかりました。


..
...
compound_stmt[stmt_ty]:
    | &('def' | '@' | ASYNC) function_def # コレ
    ...
...
..
.
# コレ
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 {
        ...
    | ASYNC 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
        ...
コードを読んでも全く理解できません、でも大丈夫(^-^)v
“def”を”関数”にするだけなので、中身は同じで良いはず。
コピペして、該当箇所を書き換えます
.
..
...
compound_stmt[stmt_ty]:
    | &('def' | '@' | ASYNC) function_def
    | &("関数" | '@' | ASYNC) function_jpdef # 追加
    ...
...
..
.
# 追加
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 {
        ...
    | ASYNC "関数" n=NAME '(' params=[params] ')' a=['->' z=expression { z }] ':' tc=[func_type_comment] b=block {
        ...

ドキュメントによると、python.gram変更後はmake regen-pegenを実行してください、と書いてあります。
しかし、マルチバイト文字列が使われることは想定されていないので、このままだとmake regen-pegenは失敗してしまいます。
失敗しないよう、make regen-pegen実行時に使用されるスクリプトを少し書き換えます。(Tools/peg_generator/pegen/c_generator.py)

.
..
...
 def visit_StringLeaf(self, node: StringLeaf) -> FunctionCall:
        val = ast.literal_eval(node.value)
        # or val in ['関数'] を追記
        # ほんとは正規表現が良いですが、手を抜きました^p^
        if re.match(r"[a-zA-Z_]\w*\Z", val) or val in ['関数']:
            ...
...
..
.

書き換えが完了したらmake regen-pegenを実行します。
parse.cとparse.new.cが生成されれば成功です。

$ 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 
Pythonをビルドして再インストール
$ make clean
$ make install 

“def”の代わりに”関数”を使って関数を定義してみる

関数 こんにちは():
    print('こんにちは')
こんにちは()
>>> こんにちは

無事、”def”の代わりに”関数”を使用することができるようになりました。
同じ要領で卵焼きコードで使われているPython文法を日本語にしていきます。

日本語化したPython文法

【 def 】
関数 function_name():
    ...
==================================================
【 class 】
クラス ClassName:
    ...
==================================================
【 if - else 】
もし 条件:
    条件が真の場合の処理
それ以外:
    条件が偽の場合の処理
==================================================
【 try - except - finally 】
例外が発生しそうな処理:
    ...
例外をキャッチ:
    ...
必ず実行する処理
    ...
==================================================
【 for 】
繰り返し i in rage(10):
    ...

卵焼きを作るコードを日本語化

改造したPythonでできるだけ日本語化したコードがこちらです。

コンソール出力 = print
クラス 料理失敗(Exception):
    pass
クラス 卵:
    中身 = ['白身', '黄身']
    混ぜた回数 = 0
    割られているか = 'いいえ'
    溶かれているか = 'いいえ'
    炒っているか = 'いいえ'
    関数 割る(自分):
        自分.割られているか = 'はい'
        return 自分.中身
クラス 良い卵(卵):
    うまみレベル = 100
クラス 悪い卵(卵):
    うまみレベル = 0
クラス 油:
    種類 = {
        'サラダ油': 0,
        'オリーブオイル': 1,
        'ごま油': 2,
    }
    関数 __init__(自分, 油の種類=''):
        自分.種類 = 油の種類
クラス ボウル:
    関数 混ぜる(自分, 卵, 混ぜる回数=100):
        もし 卵.割られているか == 'いいえ':
            raise 料理失敗('割らなきゃ混ぜれない!')
        コンソール出力(f'{混ぜる回数}回混ぜましょう')
        繰り返し 添字 in range(混ぜる回数):
            卵.混ぜた回数 += 1
            コンソール出力(f'{卵.混ぜた回数}回目')
        卵.溶かれているか = 'はい'
        return 卵
クラス フライパン:
    油 = ''
    関数 __init__(自分, フライパンに敷く油):
        もし フライパンに敷く油.種類 != 油.種類['サラダ油']:
            raise 料理失敗('サラダ油じゃなきゃダメ!')
        自分.油 = フライパンに敷く油
    関数 炒る(自分, 卵):
        もし 卵.溶かれているか == 'いいえ':
            raise 料理失敗('卵を溶いてからじゃなきゃダメ!')
        卵.炒っているか = 'はい'
        return 卵
関数 味見(料理):
    もし 料理.うまみレベル > 80:
        コンソール出力('うまい!うまい!うまい!うまい!')
    もし 80 >= 料理.うまみレベル  料理.うまみレベル:
        コンソール出力('まずい!')
### 料理開始
例外が発生しそうな処理:
    コンソール出力('良い卵を用意します')
    卵 = 良い卵()
    コンソール出力('ボウルを用意します')
    ボウル = ボウル()
    コンソール出力('卵を割ります')
    中身 = 卵.割る()
    コンソール出力(f'{中身}が出てきました')
    コンソール出力('ボウルに卵を入れて溶きます')
    溶いた卵 = ボウル.混ぜる(卵, 混ぜる回数=10)
    コンソール出力('フライパンにサラダ油を敷きます')
    サラダ油 = 油(油の種類=油.種類['サラダ油'])
    フライパン = フライパン(サラダ油)
    コンソール出力('フライパンに卵を入れて炒ります')
    炒った卵 = フライパン.炒る(溶いた卵)
    もし 炒った卵.炒っているか == 'いいえ':
        raise 料理失敗('卵に火が通ってなきゃダメ!')
    それ以外:
        コンソール出力('料理成功😏')
    味見(炒った卵)
例外をキャッチ 料理失敗:
    コンソール出力('料理失敗!')
必ず実行する処理:
    コンソール出力('料理は楽しい')

実行結果

良い卵を用意します
ボウルを用意します
卵を割ります
['白身', '黄身']が出てきました
ボウルに卵を入れて溶きます
10回混ぜましょう
1回目
... 省略 ...
10回目
フライパンにサラダ油を敷きます
フライパンに卵を入れて炒ります
料理成功😏
うまい!うまい!うまい!うまい!
料理は楽しい

最後に

どうでしょう、この日本人の脳みそに直接語りかけてくるようなソースコードは。
プログラミング初学者の方が見ても分かりやすい文法になったことと思います。
本当はpass や __init__、returnも日本語にしたかったのですが、どうにもうまくいかず諦めてしまいました。
時間のある方、ただのPythonに飽きた方は僕の代わりにPython日本語化にチャレンジしてみてください。

Fabeee編集部

Fabeee編集部

こちらの記事はFabeee編集部が執筆しております。