Upgrade to Pro — share decks privately, control downloads, hide ads and more …

cafebabepy PyCon JP 2018

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for yotchang4s yotchang4s
September 18, 2018
1.1k

cafebabepy PyCon JP 2018

cafebabepyというJVM上で動くPython3処理系を実装しています。 本セッションではPython3の言語仕様に悪戦苦闘し、どのように実装していったのかをお話します。

Avatar for yotchang4s

yotchang4s

September 18, 2018
Tweet

Transcript

  1. お前誰よ ❖ yotchang4s (よっちゃん) ➢ 澁谷 典明 (Yoshiaki Shibutani) https://twitter.com/yotcang4s

    ❖ Python歴 ➢ 1年5ヵ月くらいの初心者 (未だにfor (x in range(10)): print(x)と書いてしまうことがある… ❖ 所属 ➢ 株式会社エフ・コード
  2. 字句解析器から構文解析器への受け渡し 字句解析器 (Lexer) if 1 == 1 : <NEWLINE> <INDENT>

    print ( "hello" <DEDENT> <NEWLINE> ANTLR v4 if 1 == 1: print("hello") ) ポイントは論理的なトークンである<INDENT>と<DEDENT>。 Pythonでは字句解析でインデントの始まりと終わりを作る。 トークンストリーム 具象構文木 構文解析器 (Parser)
  3. 字句解析におけるインデントの処理方法 int indent = getIndentCount(spaces); // インデントの幅を計算 int previous =

    this.indents.isEmpty() ? 0 : this.indents.peekFirst(); // 前回のインデント幅 CommonToken newLine = new CommonToken(NEWLINE, "<NEWLINE>"); emitOnly(newLine); if (indent > previous) { // 前回のインデントより今回のインデントの方が大きければインデント開始 this.indents.addFirst(indent); // 今回のインデントを追加 CommonToken indentToken = new CommonToken(INDENT, "<INDENT>"); emitOnly(indentToken); } else { // 前回のインデントより今回のインデントの方が小さければインデント終了 (デデント) while (!this.indents.isEmpty() && this.indents.peekFirst() > indent) { CommonToken dedentToken = new CommonToken(DEDENT, "<DEDENT>"); emitOnly(dedentToken); this.indents.removeFirst(); // 前回のインデントを削除 } }
  4. 具象構文木 トークンのストリームから具象構文木を作る。cafebabepyでは具象構文木の作 成はANTLR v4で生成されたコードに全て任せている。 if_stmt トークンストリーム 具象構文木とは if 1 ==

    1 : <NEWLINE> <INDENT> print ( "hello" <DEDENT> <NEWLINE> ) if test 1 == 1 : suite if文の中の処理 この図はだいぶ省略をしているので 詳細は以下のif_stmtを参照 https://docs.python.org/ja/3/reference/gra mmar.html …
  5. 抽象構文木の作り方(2) PyObject visitReturn_stmt(PythonParser.Return_stmtContext ctx) { // [testlist]が無い場合、つまり「return」はreturnする値はNone PyObject value =

    this.runtime.None(); if (ctx.testlist() != null) { // [testlist]が存在する場合、つまり「return 1」はreturnする値は1 value = visitTestlist(ctx.testlist()); } // Pythonの「_ast.Return」を生成 return this.runtime.newPyObject("_ast.Return", value); }
  6. 抽象構文木の例 (if elif else文) (1) if文の条件式 条件を満たした時に実行される文 条件を満たしていない時に実行される文 つまり、if文の抽象構文木は •

    条件式 • 条件を満たした時に実行される文の • 満たしていない時に実行される文の集合 の3つを持つ。 elifはifのネストとして扱われる。
  7. 抽象構文木の例 (if elif else文) (2) >>> if 2 < 1:

    ... 3 ... elif 5 < 4: ... 6 ... else: ... 7 If Compare Num Gt Num Expr Num If Compare Num Gt Num Expr Num Expr Num test body orelse
  8. どうやって評価(eval)しているのか?(2) // evalの一部 public PyObject eval(PyObject context, PyObject node) {

    switch (node.getName()) { case "Module": // 抽象構文木のノード名 return evalModule(context, node); case "Interactive": return evalInteractive(context, node); case "Suite": return evalSuite(context, node); case "Import": return evalImport(context, node);
  9. どうやって実行(eval)しているのか?(4) 例:_ast.Returnをevalする private PyObject evalReturn(PyObject context, PyObject node) { PyObject

    value = this.runtime.getattr(node, "value"); // 抽象構文木 PyObject evalValue = eval(context, value); // 実際の値 throw new InterpreterReturn(evalValue); // 大域脱出のため例外を使用 }
  10. 複数行(ブロック)に対応したREPLの仕組み if 1 < 2: NEWLINE→通常NG 検証OK INDENT print(3) NEWLINE→通常NG

    検証OK DEDENT NEWLINE→通常OK 入力があった場合、通常パースする。成功したらそのまま評価する。 パースに失敗したら検証パースを行う。検証パースにも失敗したら構文エラー。 検証パースではREPLを通すために条件を緩くしている。
  11. Javaの世界とPythonの世界 PyObject インタフェース 型のクラス/ モジュール 型のオブジェクト (Pythonクラス) Pythonの世界 オブジェクト Javaの世界

    モジュールの オブジェクト (Pythonモジュール) Pythonは全てがオブジェクト。 Javaのクラス定義ではPythonの型のオブ ジェクトとして扱いづらいのでJavaのオブ ジェクトをPythonのクラスとして扱っている。 全てのPythonのオブジェクトはJava側では PyObjectインタフェースとして統一的に扱っ ている。
  12. JavaのPython擬似コードからPythonの世界へ builtins モジュールを作成 Pythonのクラス (PyObject)を作成 Javaのbuiltins パッケージ Javaの PyIntTypeクラス PyFloatTypeクラス

    etc... sys.modulesに builtinsを登録 @DefinePyType アノテーションが 付いているクラス を検索 PyObjectに 関数を登録 @DefinePyFunction アノテーションが付い ているメソッドを検索
  13. Pythonの特殊メソッド/属性例 ほんの一例 __module__ __rsub__ __gt__ __getitem__ __class__ __mod__ __rgt__ __setitem__

    __call__ __rmod__ __ge__ __delitem__ __new__ __mul__ __rge__ __getattribute__ __init__ __rmul__ __iter__ __getattr__ __name__ __neg__ __next__ __setattr__ __str__ __pos__ __index__ __hash__ __repr__ __invert__ __eq__ __contains__ __int__ __lt__ __bool__ __code__ __add__ __rlt__ __len__ __dict__ __radd__ __le__ __get__ __exit__ __sub__ __rle__ __set__ __enter__
  14. typeクラスってなんだろう? typeクラスから作られたオブジェクトがクラスである。 >>> a = 1 >>> type(a) <class 'int'>

    のように使うが、ここで呼び出しているのはtype.__call__メソッドである。 typeクラスの__call__は引数によって動作が変わる。 • 1つの場合はオブジェクトのtypeを返す。 • 3つの場合は新しいクラスを作成する。 以下の2つはまったく同じ結果となる。 >>> class A: b = 1 >>> A = type("A", (object,), dict(b = 1))
  15. オブジェクトの生成フロー a = A(1) type.__call__ type.__new__ A.__init__ Aクラスのオブ ジェクト >>>

    a.x 1 Aクラスのスコープや継 承関係から__call__を探 す Aクラスのスコープや継承関係から __init__を探す Aクラスに__init__がなければ object.__init__が呼ばれる。 引数にクラスが渡ってくるので そのクラスのオブジェクトを生 成 __call__に渡っ てきた引数xを 渡す
  16. Pythonの関数とメソッドについて(2) class A: def __get__(self, obj, type=None): print("Hello") とデスクリプタを定義するだけでは意味が無い。 class

    B: x = A() とすることにより >>> B.x Hello となる。つまり、他のクラスの属性にデスクリプタを設定すると、 クラスの特定の属性へのアクセスをカスタマイズすることができる。
  17. Pythonの関数とメソッドについて(3) • クラスにdefで定義するものはメソッドではなく関数である。 • 関数はデスクリプタでもある。 • 関数(デスクリプタ)を属性としてクラスに定義することにより関数を参照 する時はデスクリプタとして扱われる。 関数のデスクリプタで行っていることは第一引数にレシーバーであるオブ ジェクトをバインドしたメソッドを作成する。

    メソッドとは関数に対して参照元のオブジェクトを第一引数にバインドした関 数と言える。この第一引数にバインドしたオブジェクトこそがPythonのself の正体! つまり、デスクリプタという仕組みを用意することで関数/メソッドを統一的に 扱える。
  18. Pythonの関数とメソッドについて(4) >>> class T: ... def a(): pass ... >>>

    T.a # クラスから呼ぶとfunction <function T.a at 0x7fb827525e18> >>> tx = T() >>> tx.a # オブジェクトから呼ぶとmethod <bound method T.a of <__main__.T object at 0x7fb8275264e0>> >>> def x(self, a): ... print(self) ... print(a) >>> x.__get__(tx)(99) # デスクリプタなのでこんなこともできる <__main__.T object at 0x7fb8275265f8> 99
  19. 言語処理系作成におけるComposability(1) >>> *range(10), の結果は何になるだろうか? 答えは(0, 1, 2, 3, 4, 5,

    6, 7, 8, 9)となる。 *range(10)でイテレーターを展開した結果を元にtupleの本体であ るカンマからtupleを作っているらしい。 ※ちなみにtupleの本質は()ではなくカンマ。例(99,)や99, cafebabepyでは*range(10),に対して特殊な処理は行っていな い。 「*」でイテレーターを展開する処理、カンマでtupleを作成する処 理を作っただけである。その組み合わせがこの結果となっている。
  20. 今後の展望 • Python 3の文法/モジュールを全て実装 • 速度改善 ◦ invokedynamic命令等を使った高速化 ◦ リフレクションを極力少なく

    • PythonのコードからJavaのコードを実行 • C拡張の実行 ◦ NumPyとかSciPyがJavaから呼べると激アツでは? ◦ JRubyで出来ていそうなので参考にできるかも?