Pythonを読む

はじめに

オブジェクトに属性がどう保存されるのかを見ていきます。

ちなみにPythonの場合、メソッドも属性です。つまり、関数オブジェクトを指すオブジェクトになっています。とは言うものの、インスタンスのバインドの話があるのでメソッド呼び出し時の動作についても見ていこうと思います。

ともかく、対象とするプログラムはこちら。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
 
 
 
 
 
class Foo:
    def __init__(self, x):
        self.x = x
    
    def method(self):
        print(self.x)
 
foo = Foo()
foo.method()

解析のための道具

「はじめに」で挙げたプログラムがどのようなバイトコードに変換され実行されるかを見るわけですが、構文解析→コード生成を手動でやると手間がかかってしまいます。標準関数・標準ライブラリに便利なものがあるのでそれを利用しましょう。

まず先ほどのプログラムを文字列として定義します。

>>> src = '''
class Foo:
    def __init__(self, x):
        self.x = x
    
    def method(self):
        print(self.x)

foo = Foo()
foo.method()
'''

それをcompile関数に放り込む。

>>> co = compile(src, '<string>', 'exec')

これでコードオブジェクトが出来上がります。

バイトコードを得るにはdisモジュールを使います。

>>> dis.dis(co)
  2           0 LOAD_BUILD_CLASS
              1 LOAD_CONST               0 (<code object Foo at 0x01FAD8E0, file "<string>", line 2>)
              4 LOAD_CONST               1 ('Foo')
              7 MAKE_FUNCTION            0
             10 LOAD_CONST               1 ('Foo')
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             16 STORE_NAME               0 (Foo)

  9          19 LOAD_NAME                0 (Foo)
             22 LOAD_CONST               2 (123)
             25 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             28 STORE_NAME               1 (foo)

 10          31 LOAD_NAME                1 (foo)
             34 LOAD_ATTR                2 (method)
             37 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             40 POP_TOP
             41 LOAD_CONST               3 (None)
             44 RETURN_VALUE

1~13のあたり、もうすでに興味深いところですが一旦置いといてコードオブジェクトで調べられるものを確認します。

まず、「クラス定義」を表すコードオブジェクトは定数として格納されているようです。定数はco_constsで表示できます。

>>> co.co_consts
(<code object Foo at 0x01FAD8E0, file "<string>", line 2>, 'Foo', 123, None)

co_namesにコードで使われている「名前」が格納されています。

>>> co.co_names
('Foo', 'foo', 'method')

クラス定義に踏み込む

クラス定義はモジュール全体を表すコードオブジェクトの定数だったので、次にそれをdisってみます。

>>> dis.dis(co.co_consts[0])
  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('Foo')
              9 STORE_NAME               2 (__qualname__)

  3          12 LOAD_CONST               1 (<code object __init__ at 0x01FAD6B0, file "<string>", line 3>)
             15 LOAD_CONST               2 ('Foo.__init__')
             18 MAKE_FUNCTION            0
             21 STORE_NAME               3 (__init__)

  6          24 LOAD_CONST               3 (<code object method at 0x01FAD890, file "<string>", line 6>)
             27 LOAD_CONST               4 ('Foo.method')
             30 MAKE_FUNCTION            0
             33 STORE_NAME               4 (method)
             36 LOAD_CONST               5 (None)
             39 RETURN_VALUE

__init__メソッドの定義、methodメソッドの定義はさらに「クラス定義のコードオブジェクト」の定数です。それぞれ、1番目と3番目です。

>>> dis.dis(co.co_consts[0].co_consts[1])
  4           0 LOAD_FAST                1 (x)
              3 LOAD_FAST                0 (self)
              6 STORE_ATTR               0 (x)
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(co.co_consts[0].co_consts[3])
  7           0 LOAD_GLOBAL              0 (print)
              3 LOAD_FAST                0 (self)
              6 LOAD_ATTR                1 (x)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

ところでそれぞれのco_namesを見てみましょう。

>>> co.co_consts[0].co_consts[1].co_names
('x',)
>>> co.co_consts[0].co_consts[3].co_names
('print', 'x')

selfはいません。引数(と今回はないけどローカル変数)はco_varnamesに記録されています。

>>> co.co_consts[0].co_consts[1].co_varnames
('self', 'x')
>>> co.co_consts[0].co_consts[3].co_varnames
('self',)

なお、1番目(__init__メソッドに対応するコードオブジェクト)にはco_namesにもco_varnamesにも'x'がいますが、co_namesのは属性名、co_varnamesの方が引数名です。

しれっとローカル変数と書きましたがトップレベルの変数(fooなど)については少し特別な扱いになっているようです。importの関係だとは思いますが、属性参照の名前も入ってるのにそれを区別するっぽい情報は見当たりませんね。

おわりに

今回はとりあえず手始めにクラス定義がどのようなバイトコードになるのかを確認しました。compile関数、disモジュールを使うと自分で構文解析→コード生成をしなくてもいいのでPythonプログラムに対するバイトコードが気になったら手軽に確認することが可能になります(そんな人あまりいない)

ところで、もちろんdisモジュールで出てくるのはPython処理系の詳細なわけですが、他のPython実装だとどうなってるんでしょうね。標準ライブラリと言っても標準仕様があるわけじゃないから付けるのは必須ではないと思いますが。コードオブジェクトも実装依存だと思いますし(そもそもドキュメントが見当たらない)


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS