2024.11.25
「能動的サイバー防御」時代の幕開け 重要インフラ企業が知るべき法的課題と脅威インテリジェンス活用戦略
JVM上で動くPython3処理系cafebabepyの実装詳解(全1記事)
リンクをコピー
記事をブックマーク
澁谷典明氏:では、cafebabepyというJVM上で動くPython3処理系の実装詳解というかたちで話させていただきます。
注意! 言語処理系の実装詳解というだいぶ無茶なことをやるので、スライドが多めです。気になったところがあれば、個別に質問したり、あとでスライドを公開するので見てください。
こんなニッチなセッションにありがとうございます(笑)。よっちゃん(yotchang4s)です。
本名は澁谷典明と言います。Twitterアカウントはyotchang4sでやっているので、よかったら見てください。
Python歴はまだ1年5ヶ月ぐらいで、実際初心者です。未だにfor文のところに( )を付けてしまうというくせがあって、シンタックスエラーで落ちます。そんなことをやっています。所属は、株式会社エフ・コードというところで今働いています。
本日のおしながきです。1.Pythonの抽象構文木(AST)を作成する話、2.抽象構文木を評価(eval)する話、3.PythonとJavaの境界線の実装の話、4.Pythonの深くて美しい言語仕様の話、5.まとめとなっております。
cafebabepyについて、名前を聞いたことがある方はいらっしゃいますか?
(会場挙手)
けっこういた(笑)。ありがとうございます。
ちなみにGithubのリポジトリはhttps://github.com/cafebabepy/cafebabepyでとっているので、そこに随時プッシュしています。
実はcafebabepy.orgドメインもとっています。Javaをやっている人はわかると思うんですけれども、org. cafebabepyパッケージをどうしても使いたかっただけです。アクセスしてもなにもありません。
じゃあcafebabepyとはなんだろうというところで、JVM上で動くPython3の処理系です。
名前の由来はJavaのクラスファイルのマジックナンバーであるcafebabeから来ています。Javaのクラスファイルをバイナリエディタとかで表示すると、頭にcafebabeとついています。そこから名前をとりました。
開発を開始してからちょうど1年5ヶ月。さっきお話しした「Python初心者です」「1年5ヶ月しかやっていません」というのと同じ時間です。
私、よっちゃんが1人でつくっています。ちなみに#cafebabepyで開発状況を随時つぶやいているので、暇があったときに見てみてください。
また注意です。まだまだ全然実装途中です。Python3は完全には動きません。まだほぼすべてのモジュールがimportできません。標準モジュールはPyPyからもってきているんですが、それがまだimportするのが大変だということです。
開発の動機です。「Python3でJythonを使うぞ!」しかし、Python2.7までしか対応しておりません。そして2015年から更新が止まっています。だったら自分でつくればいいわけです。
開発のモチベーションは、先ほども言ったとおりPython初心者なので、「Pythonわかりません」という状況です。「でも知りたいです」と。Pythonのインデントとかが好きで。Pythonの処理系をつくったらPythonがわかるはずだと。あと一番大きいのは楽しい! ということですね。
では、さっそくいきます。Pythonが動く本当にざっくりとした仕組みなんですが、少し細かい話になってきます。
まずインプットされたソースコードについて、字句解析というものをします。そして字句(トークン)のストリームを作ります。
そしてつくった字句のストリームから具象構文木というものを作ります。その具象構文木から構文解析して、先ほども出た抽象構文木(AST)というものを作ります。
抽象構文木をそのあとeval(評価)します。これがcafebabepyのすごく単純なインタプリタです。ちなみにCPythonだとバイトコードを生成してVirtual Machine(VM)上で動かすということをやっていたはずです。
では、抽象構文木をつくりましょう。まず字句解析をします。ソースコードの文字列を解析して、構文解析における最小の単位となっているトークン(字句)の並び(ストリーム)をつくります。
ストリームをつくるのはけっこう大変なんですが、ANTLR v4というすごくいいツールがあって、これはPythonのコードも出力できるのでたぶん知っている方もいらっしゃると思うんですが、それで生成した字句解析器を、さらにカスタマイズしています。
Pythonというのは、ご存知のとおりインデントでブロックを表現します。関数とかif文とか。ですので、字句解析の時点でインデントを論理的なトークンに変換する必要があります。
構文解析器は、スペースとかそういったものを処理するのではなく、トークンに対して処理をどんどん行っていくので、字句解析で、先ほどの話の通り空白文字であるインデントをインデントトークンというものに変換する必要があります。
字句解析器から構文解析器への受け渡しというところで、まず字句解析から始めます。
if 1 == 1: print(“hello”)
というソースコードが入力として入ってきます。そうしたら、字句解析器(Lexer)に通します。そうすると、トークンストリームは“if、1、==、1、:、<NEWLINE>”というようになります。<NEWLINE>も論理的なトークンで、改行を示します。ここでポイントなのは、print関数がここにありますが、ここにインデントされていますよね。
ここで改行したら、<INDENT>というトークンを入れます。これは論理的なトークンです。そのあとprint、(、”hello”、)。ここがポイントで、そのあとに<DEDENT>というのを入れます。1回インデントをして、ブロックが終わったらそれはDEDENTというかたちで、インデントを抜けます。
そしてそのトークンストリームを今つくれました。そうしたら、ANTLR v4にかませて具象構文木をつくって、構文解析器(Parser)に渡します。実際は具象構文木とParserは順番が逆かもしれないんですが、ここでは便宜上こうしています。
PyConなのにJavaのコードです(笑)。少し見づらいんですが、これは字句解析器です。ポイントはインデントの現在の幅を計算します。スペースとタブの2つです。
前の文のインデント幅を取得します。そして、前回のインデントより今回のインデントのほうが大きければ、インデント開始(INDENT)となります。前回のインデントより今回のインデントのほうが小さければ、インデント終了(DEDENT)になります。
それをトークンとしてどんどん登録していきます。これがPythonにおけるインデントの処理方法です。基本はCPythonも同じことをやっているかと思います。
具象構文木とは。トークンのストリームから具象構文木というのをつくります。実はcafebabepyでは具象構文木を自力ではつくっておりません。ANTLR v4で生成されたコードにすべて任せています。
トークンストリームはさっき出てきたので、これはこれとしてよくて、それをANTLR v4で具象構文木にします。これはかなり省略した図なんですが、なにかルートがあって、ifというのがあって、testというルールがあって、その中には1 == 1というのがあるよ、というツリー構造になっています。
あとsuiteというifがTrueだったときの処理。それから、ここには書いていないですが、else文のorelseが入っています。だいぶ省略しているので、もし詳しく知りたければ、資料をアップしたときにここにアクセスすると見ることができます。
ではさっきから言っているANTLR v4とは何なのという話です。
ここは少し難しい話で、ALL()という構文解析に基づくパーサジェネレータです。EBNFという構文ルールを書く記法があって、拡張子.g4ファイルにそれに似たかたちで記述すると、字句解析器と構文解析器を生成することができます。
構文解析器にかけると具象構文木がANTLR v4でジェネレートされたJavaのコードにて、そこからオブジェクトを生成して、そのあとにvisitorとかactionというものによって、具象構文木を走査することができます。ちなみにcafebabepyはvisitorを採用しております。ポイントとしては、visitorというのはただ単純にGoFのvisitorパターンのことです。
もう1つが、先ほども少しお話ししましたように、実はANTLR v4はJavaのコードだけではなく、Pythonのコードもジェネレートすることができます。なのでみなさん、もし構文解析したければPythonでもできますよ、というところです。*.g4ファイルの例は、あとで見てください。
Pythonの構文ルールについてです。ポイントは上に書いてあるとおりです。
Pythonの構文はLL(1)というもので、Pythonの構文を設計する際にも、LL(1)というのが守られています。
ただし、さっきINDENT、DEDENTといった論理的なトークンを作るというお話をしましたけれども、その通り字句解析して、論理的なトークンにしたあとのトークンストリームからつくる構文が、LL(1)というだけです。ここは少し注意です。ですので、PythonはLL(1)だよと言うと、怖い人からつっこみが入るので気を付けてください(笑)。
LL(1)のParserというのは、少し難しいですが、再帰下向き構文解析というので実装できます。じゃあLL(1)の1ってなんだよという話で、LL(1)は1個のトークンを先読みするだけで構文が決定できます。そういう文法です。
LLとは、左再帰というものができず、LL(1)はバックトラックが発生しない。なんのこっちゃって話ですね。もう少し説明すると、再帰下向き構文解析とは、構文木があるとして、それを考えたときに、走査方法が根元から枝へと解析を進めていく解析方法ということだけ覚えておけば大丈夫です。
LLについて左再帰することができないと言いましたけれども、例えばExprがExpr+Termという構文規則があったときに、ExprはExprである、ExprはExprであると、左側でどんどん無限再帰するんです。それができませんよというのがLLです。
バックトラックというのは例えばLL(3)、3個のトークンを先読みしますよって言ったときに先読みはするんですけれども、もしそこが先読みした結果、構文規則に合わなかった場合にはまた戻って解析をやり直す必要があるんですね。そのことをバックトラックと言います。
Pythonの構文ルールはEBNFに近いかたちでドキュメント化されています。
このリンクを見たらPythonの構文ルール完全マスターですね。すごくわかりやすく書いてあるので、見てみてください。けっこうおもしろいですよ。
ANTLR v4もEBNFに近いかたちで書けますし、ALL()に対応しているので、.g4ファイルに記述する内容はほぼ写経すればいいだけです。ただし文字列等のUnicode対応は除くということですね。
これはすごく難しくて、Pythonの仕様として「Unicodeの何番から何番まで使えますよ」というのを書かないといけないので、そこが大変なところです。cafebebepyではそこの対応はしておりません。そのうちします。
じゃあ、なんで抽象構文木を作るのでしょうか? 抽象構文木というのは具象構文木ともちろん違います。明確に言えばプログラムの実行に必要のない情報を取り除いて意味のある情報のみを取り出して抽象化した、木構造のデータ構造なんですね。
例えば、if True: passという構文があったときに、「:(コロン)」はあくまでも構文解析のために必要なトークンなんです。だから抽象構文木ではいらない情報なわけですよ。なので、抽象構文木にすることによってコロンなどは関係なく言語の意味だけを抽出できます。そのあとで出てくる評価(eval)する工程が非常に楽になります。
じゃあ、Pythonの構文木とは何だろう? Pythonでは抽象構文木にアクセスするためのAPI「_astモジュール」が仕様化されてて、ドキュメントもありますと。重要なのはさっき言っていた_astモジュールで、ここにすべてのPythonの抽象構文木のノードがあります。
astは、「(アンダースコア)」が付いているだけあってCPythonでは Pythonで書かれていないので、cafebebepyでは自力で全部作成します。_ast.While、_ast.For、_ast.Returnとかいろいろあるんですが、そういう抽象構文木は自分の手で心を込めて書いています。
(会場笑)
じゃあ抽象構文木ってどうやって作っていくの? というところですね。具象構文木から抽象構文木を作るんですけれども、cafebebepyではANTLR v4を使っているので、前述のとおり具象構文木は自動生成されます。
その具象構文木を先ほどもお話ししたvisitorパターンを使ってどんどんvisitしていって抽象構文木を作成します。
例として、return_stmt。これはreturn文です。この[ ]はどういう意味かと言うと、return_stmtはreturnという文字が入ったあとにtestlistという構文ルールが0回もしくは1回出てくるというルールです。つまりreturn文とは戻り値を指定したりしなかったりするっていう意味ですね。
この場合だとどうやって作成するのでしょうか? 結局またJavaにコードになってしまうんですが……。
もしtestlistがなかったらreturnはNoneになるので、最初はNoneですと。testlistが存在するときだけtestlistというルールですね。visitorに対してvisitしていきます。値を取得して_ast.Returnを作成して、上に返すということをやっています。
上に返すと言っていますが、何なの? という話で。いろいろあるんですが、結局何をやっているかと言うと、最初に例えばvisitFile_inputというルートがあります。そのなかでvisitStmtを呼びます。
さらに下の構文に入ります。
visitなんとかかんとかとありますけれども、さらにvisitなんとかかんとかを呼びます。この中で抽象構文木を作って戻り値で上に返します。またここで抽象構文木を返します。その結果作成されるのが、右の図になっていて、ModuleというASTがあって、stmt、Expr等々いろいろあるんですが、こういうツリー構造になります。
これが抽象構文木の例です。
これは実はif文の抽象構文木です。IfというclassであるASTがあって、その下にCompareというのがあって、それをNumとNumで比較していますというだけで。それでif文の条件を満たしたときに実行される文、条件を満たしていないときに実行される文となっております。
ここを見ると、条件を満たさない場合というのは、結局elseですよね。つまり自分的にcafebebepyを作っているときにハマりポイントだったのは、elifは結局Ifのネストです。elseの中にIfがあります。
詳細化したのがこれです。
あんまり意味のないコードですが。ちょっとインデントがずれていますね。if、elif、elseとあったときに作られる抽象構文木はIf、さっき話したCompare、Num、小なり、Num、body。testが成功したらbodyはNumですと。それがこれですと。
orelse、ここですね。またIfが入ってまたCompareとかがあって。あとelseで構文木が作られます。ちょっとわかりづらいですが、こんなかたちになっています。
抽象構文木を評価(eval)します。どうやってevalしているのでしょうか? 冒頭にもあったとおりシンプルに実行するためにcafebebepyではすべてがevalメソッドを起点とするインタプリタになっています。
抽象構文木をどんどん辿っていって、その子をさらにevalすることによって木をどんどん走査していきます。これは、すごく再帰的な構造になっています。ちなみにevalメソッドは各ノード専用のeval処理に振り分けているだけです。
どういうことかと言うと、至極単純な話で。ノードの名前を取得して、そのノードがModuleなのかSuiteなのかImportなのかというので、例えばImportだったらevalImportというメソッドを呼んでいるだけです。
Javaのコードです。
じゃあ、どうやってevalしているのか? というところをもうちょっと掘り下げてみると、最初にevalを呼びます。
これはif文です。if文の中には条件判定をするためのtestという抽象構文木がここの中に入っています。testというのは抽象構文木なので、testがTrueかFalseかという結果がほしいわけですね。
そこでtestをさらにevalするんですね。そのことによって実行値を取得できるので、その結果がTureであるかを判定します。これはここに書いてあるbodyとorelseも同じで、testをevalした結果がTrueかFalseかによってbodyが実行されたり、orelseが実行されたりします。
あまり例がよくないですが、evalの例として、まずgetattrから抽象構文木を取得します。
このままだと抽象構文木なのでevalを再帰的に呼び出します。実際の値がevalValueなので、それを使いましょうというだけですね。ここはreturn文なので大域脱出するために例外に挙げてます。あんまりよろしくないですね(笑)。
突然ですがREPLの話にいきます。
REPLとはRead Eval Print Loopの略で、pythonってコマンドを叩いたらみなさんPythonのコードを書けますよね。それのことです。通常REPLを実装するにあたって、1行1行実行するREPLというのはただ単純に入力があったら解析して実行するだけなんですけれども、このままだと複数行に渡るブロックの処理というのができないんですよ。
まずif 1 < 2:と入力しますよね。それを入力した段階でEnterを押すと、通常だとPythonは構文ルールとしてNGなわけですね。ただifの中に処理を書かないといけないじゃないですか。なので特殊処理をしてあげないといけないんですね。
ここで「. . . 」とあって、これは実は第二プロンプトと言います。じゃあこれはどうやっているの? というところで。
ここがものすごく考え抜いたところです。
もし入力があった場合、普通にパースします。成功したらそのまま評価して終わりで、もしパースに失敗したら検証パースというのを行います。検証パースに失敗したら構文エラーとして扱います。
検証パースとは何かと言うと、REPLを通すために条件をゆるくしているパーサーを作っています。例えば先ほども例に出た、if 1 < 2:というのがきたときには、このとおり最後NEWLINEで通常パースできません。でも検証はOKとします。ちょっとゆるいんですね。
さらにINDENTが入ります。print(3) NEWLINEというのが入力値としてやってきます。この場合も通常はNGですが、検証パースはOKになります。
最後に空白の1文ありますが、ここにきた段階でDEDENTが入って、通常パーサーがOKで評価をして3になります、ということをやってます。たぶん同じ方法をJythonとかでも使っているんじゃないですかね。
PythonとJavaの境界線の実装の話にいきます。JavaとPythonについて。PythonとJavaの世界にはインピーダンスミスマッチがあります。じゃあ、どうやってJavaの世界でPythonを実装しているのでしょうか?
Pythonはすべてがオブジェクトです。
Javaのクラス定義ではPythonの型のオブジェクトとして非常に扱いづらいので、JavaのオブジェクトをPythonのクラスとして扱っています。すべての PythonオブジェクトはJava側ではPyObjectインターフェースとして統一的に扱っています。左側がJavaの世界で、右側が Pythonの世界です。
結局何かっていうと、Javaのクラスを定義しますと。 PythonですがJavaで書かないといけないので、型のクラスを定義してそこから作ったオブジェクトを Pythonのクラスとしています。そこが Pythonの世界です。これはモジュールも同じです。そこからオブジェクトを作ったりしています。ここを話し出すと40分くらいかかるので(笑)。
じゃあどうやっているのか。Python擬似コードを書かないといけませんという話です。至極単純で、アノテーションというのを使っています。デコレーターではありません。
ここの例だとbuiltins.intというのをタイプとして指定して、その中には@DefinePyFunctionというもので__add__メソッドを定義していますよと。それだけです。
実際にどうやってるの? というところで、builtinsモジュールを作成して、先ほどもあった@DefinePyTypeアノテーションが付いているクラスを検索します。
Javaのbuiltinsパッケージの中を検索しに行って。Javaの世界の話になってしまいますが、いろんなクラスが取得できますと。
それをもとにPyObjectというのを作って、そこから@DefinePyFunctionというのをクラスから、例えばPyIntTypeから探しに行って、取得できたらPyObjectのスコープに関数をどんどん登録していきます。最後、sys.modulesにbuiltinsを登録します。
Javaは静的型付けなのでJavaのメソッドをPythonのような動的型付き言語のメソッド関数として振る舞わせることはすごく難しいです。
cafebebepyでは動的にメソッドの呼び方を変えるためにリフレクションを使っています。ただリフレクションはすごく遅いので、今後はinvokedynamic命令、indyですね。この命令に置き換える予定です。
Pythonの深く美しい言語仕様というところに入っていきます。ここからは処理系を作るうえで必要なPythonの言語仕様についての話がちょっと多くなります。Python自体を知ってもらうことによって処理系では何をしているのかを理解してもらえればいいと思っています。
Pythonには特殊メソッドがありますよね。
特殊メソッドとは処理系とPythonのコードとの間の決まりごとです。例えばlen関数ひとつとっても、引数のオブジェクトの長さを取得することができますよね。len関数内部では、オブジェクトの__len__メソッドを呼び出して長さを取得するということをやっています。
つまり自分で作ったクラスのオブジェクトだとしても、__len__メソッドさえ定義していればlen関数に渡すとその長さを取得できるというものです。ちなみに、__len__はだんだーれん(dunder len)と呼ぶこともあるらしいです。
特殊メソッド、ほんの一部ですが(笑)。
(スライドを指して)多い!
(会場笑)
Pythonの__call__についてです。広義の意味で言えば__call__を持つオブジェクトはすべて関数です。異論はだいぶ認めます(笑)。
__call__を持っているオブジェクトを呼び出すときには、みなさんご存知のとおり__call__を省略できます。functionクラスも__call__を持っていて、lambdaの実体もfunctionです。例えばここのコード例で言うと、class A: passがあって、Aを生成していますと。Aのオブジェクトを生成しているんですが、実際はA()はtypeクラスの__call__を呼んでいるだけです。
じゃあtypeクラスとは何だろう? 実はtypeクラスから作られたオブジェクトというのがクラスです。通常typeの中にオブジェクトを入れて型を取得するんですけれども、ここで呼び出しているものは実際はtype.__call__メソッドです。
typeは引数によって動作が変わって。1つの場合はさっき言ったとおりで、3つの場合は新しいクラスを生成します。実はここで定義している2つの処理はまったく同じ結果となります。というのを踏まえたうえで、じゃあ__call__が呼ばれるときに、どうやってtypeクラスからオブジェクトが生成されるのか? というところを紹介します。
例えばclass Aというのを定義して、__init__というのを定義しましたと言ったときに、じゃあどうなの? というところで。オブジェクト生成フローです。
まず、type.__call__を呼びます。その中で、実際にはtype.__new__を呼ぶんです。
__new__を呼ぶと引数にクラスが渡ってくるので、それをもとにAクラスのオブジェクトを作ります。そのあとに、作成されたオブジェクトのクラスの__init__を呼ぶ、ということをします。__call__に渡ってきた引数も一緒に渡してです。もし存在しない場合は、継承関係の上にあるobject.__init__が呼ばれます。
今日の重要ポイントです。
__get__はPythonでメソッドを作るときに重要な特殊メソッドです。これを理解できれば、メソッドの引数にselfを付ける意味がわかります。Pythonの言語仕様の綺麗なところがわかってもらえるのかなと思っています。__get__はデスクリプタと言っていて、詳細に説明するとそれだけで多分45分かかってしまうので、簡単に紹介させてもらいます。
ここでclass A:に__get__を定義します。
print("Hello")を呼んでいます。定義するだけではどうしようもないんです。class B:の、例えば、x = A()のオブジェクトを生成して、こうすることによってB.xと参照すると、なぜかHelloが出力されるんです。
これはどういうことかと言うと、ほかのクラス。ここで言うと、Class B:です。デスクリプタを設定すると、クラスの特定の属性へのアクセスをカスタマイズできます。こういう仕組みがあるんです。
実はクラスにdefで定義するものは、メソッドでなくて実は関数です。
さらにもう1つ、関数はデスクリプタでもあります。関数(デスクリプタ)を属性として、クラスに定義します。属性としてクラスに定義するということは、関数を参照するときには、その関数はデスクリプタとして扱われます。
じゃあ、関数のデスクリプタで行っていることは何だろうと言うと、第一引数にレシーバーであるオブジェクトをバインドしたメソッドオブジェクトを作ります。ここがポイントです。
つまりメソッドというのは関数に対して、参照元のオブジェクトを第一引数にバインドした関数と言えます。この第一引数にバインドしたオブジェクトこそがPythonのselfの正体です。それが渡ってきます。つまり、デスクリプタという仕組みを用意すると、関数とメソッドを統一的に扱うことができます。
ちょっとややこしい例ですが、クラスを作って、functionを定義して、クラスからaを呼ぶとfunctionです。
オブジェクトを作ってaを参照すると、bound methodが作られます。
こういうこともできます。xという関数を定義して、aという引数を持っています。実は直接呼ぶこともできて、x.__get__と呼んで(tx)というオブジェクトを渡して、その返ってきたmethodに(99)という引数を渡すと、selfがprintされて、99がprintされるということができるんですが、みなさんは絶対にやらないでください。
Pythonにおいて、メソッドの第一引数にselfを明示的に書くことは、関数とメソッドを統一的に扱うためにあります。そこがすごく美しいところだと、個人的には思っております。
Composability。*range(10),の結果は何になるでしょうか? 答えは(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)のtupleです。
*range(10)でイテレーターを展開します。それをもとにtupleの本体であるカンマからtupleを作っているらしいです。ちなみにtupleの本質は( )ではなくカンマです。例えば99,と付けると、それは(99,)のtupleになりますよね。
実はcafebabepyではそれに対して特殊な処理は一切行っておりません。 「*」でイテレーターを展開する処理と、カンマでtupleを作成する処理を作っただけです。その組み合わせがこの結果になっています。
言語処理系では、小さい部品を作ってそれを組み合わせることによって、大きくはないですが、今のような組み合わせの処理が出来るようになることが非常に多いです。実際、実装が面倒くさいなと思ったときでも、すでに作ってある小さな部品を、ちょっと組み合わせるだけでその仕様が満たせるということがあります。
実装すら必要ない場合があるんです。作っていたら、いつの間にか言語処理系としてうまく動いていたというようなパターンがあるんです。実はここが言語処理系の面白さで、そしてComposability(組立可能性)があります。
最後に今後の展望として、Python 3の文法/モジュールをすべて cafebabepyでは実装します。きっとZopeも動くし、きっとDjangoも……2年待ってください(笑)。
(会場笑)
あと、速度改善です。invokedynamic命令等を使った高速化と、リフレクションをより少なくということをやります。PythonコードからJavaコードを実行させます。そのうち、C拡張の実行もしたいです。NumPyとかSciPyがJavaから直接呼べると激アツではと。JRubyで出来ているっぽいので、参考にできるかなと思っています。
cafebabepyのために再コンパイルとか、そういう訳ではなくて、普通にPythonで使っているC拡張をそのまま持ってきて動かせるようにしたいなと思っております。
まとめ。時間の都合上、まだまだ伝えきれない実装や仕様がいっぱいあるんですが、その中から苦労して実装したものや、言語処理系を作っていく中で知ったPythonの美しさをピックアップしてみました。
けっこう流しましたが、実は言語処理系を作るのは、泥臭さとの戦いです。ただ、自分で作った処理系で、ほかの人が書いたコードが動くことが、すごく嬉しいことです。
cafebabepyで最初に動いたモジュールは、thisです。import thisをすると、みなさんご存知「The Zen of Python」が表示される。これが最初に動いたモジュールです。これは感動ものです。今後もその初心を忘れず開発していきたいものです。今後cafebabepyを温かく見守ってみてください。ご清聴ありがとうございました。
司会者:発表ありがとうございました。時間の都合上、質疑応答の時間は取れないので、このあとセッション中休憩時間等に発表者の方へ直接質問等をしてください。
ありがとうございました。
(会場拍手)
関連タグ:
2024.11.21
40代〜50代の管理職が「部下を承認する」のに苦戦するわけ 職場での「傷つき」をこじらせた世代に必要なこと
2024.11.13
週3日働いて年収2,000万稼ぐ元印刷屋のおじさん 好きなことだけして楽に稼ぐ3つのパターン
2024.11.20
成果が目立つ「攻めのタイプ」ばかり採用しがちな職場 「優秀な人材」を求める人がスルーしているもの
2024.11.20
「元エースの管理職」が若手営業を育てる時に陥りがちな罠 順調なチーム・苦戦するチームの違いから見る、育成のポイント
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.11.19
がんばっているのに伸び悩む営業・成果を出す営業の違い 『無敗営業』著者が教える、つい陥りがちな「思い込み」の罠
2024.11.18
20名の会社でGoogleの採用を真似するのはもったいない 人手不足の時代における「脱能力主義」のヒント
2024.11.13
“退職者が出た時の会社の対応”を従業員は見ている 離職防止策の前に見つめ直したい、部下との向き合い方
2024.11.21
初対面の相手から本音を引き出す「核心質問」のやり方 営業のプロが教える、商談成功のカギを握る質問力アップのコツ
2024.11.22
40歳以降の「つみたてNISA」と「iDeCo」運用の優先順位 老後のための資産形成のポイント