LoginSignup
5
7

More than 5 years have passed since last update.

クラスメソッドとデコレータ、の深い話

Posted at

はじめに

Pythonでクラスメソッドを定義する際、以下のようにデコレータを指定します。(比較としてインスタンスメソッドも書いています)

class Foo:
    def instance_method(self):
        print('instance method')

    @classmethod
    def class_method(cls):
        print('class method')

ところでデコレータの説明を見ると以下のようにあります。

デコレータ式は関数を定義するとき、関数定義の入っているスコープで評価されます。

評価?どゆこと?(。´・ω・)?

というわけで、クラス定義を書くとPythonは何をしているのかについて説明します。なお、以降の出力はCPython 3.6.4です。バージョンによって多少出力が変わると思いますが大まかには同じだと思います。

ともかくバイトコードを出力してみる

Pythonはcompile関数を使ってソースコード(の文字列)を実行可能なコードオブジェクトに変換できます。
また、disモジュールが用意されており、コードオブジェクトを渡すとバイトコードが表示されます。
つまり、compile関数とdisモジュールを使えばソースコードがどのようなバイトコードに変換されて実行されるのかがわかります。というわけでやってみましょう。

>>> src = '''
... class Foo:
...     def instance_method(self):
...         print('instance method')
...
...     @classmethod
...     def class_method(cls):
...         print('class method')
... '''
>>> co = compile(src, '<string>', 'exec')
>>> import dis
>>> dis.dis(co)
  2           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               0 (<code object Foo at 0x031C75F8, file "<string>", line 2>)
              4 LOAD_CONST               1 ('Foo')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               1 ('Foo')
             10 CALL_FUNCTION            2
             12 STORE_NAME               0 (Foo)
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE

ここで出力されたものはソースコードのどの部分に当たるかというと、class Foo:です。
「中身の部分は?」の前にこのバイトコードがどう実行されるのかについて簡単に説明しましょう。

Python VM超要約入門

PythonのVMはスタックマシンです。LOADなんとか系のバイトコードでスタックに値が積まれます。また、MAKE_FUNCTIONやCALL_FUNCTIONはスタックに積まれている値を使って命令が実行されます。

まず、「LOAD_CONST 1」まで実行されるとスタックは以下のようになります。

'Foo'
Fooコードオブジェクト
__build_class__関数オブジェクト

MAKE_FUNCTIONはスタックの上2つを取り出し関数オブジェクトを作成します(1つ目が関数名、2つ目が関数として実行するコードオブジェクト)。つまり、MAKE_FUNCTION実行後のスタックは以下のようになります。

Fooコードオブジェクトに対する関数オブジェクト
__build_class__関数オブジェクト

「CALL_FUNCTION 2」とは「関数を引数2つとして実行してください」という意味です。CALL_FUNCTION直前のスタック状態は以下のようになっています。

'Foo'
Fooコードオブジェクトに対する関数オブジェクト
__build_class__関数オブジェクト

実行される関数は、引数を取り除いたうえでスタックの一番上にあるもの、今の場合は__build_class__です。また引数は下から順に第1引数、第2引数、となります。つまり、__build_class__(Fooコードオブジェクトに対する関数オブジェクト, 'Foo')のように関数が実行されることになります。

__build_class__がやっていることを要約すると以下のようになります。

  1. 渡された関数オブジェクトを実行し属性辞書を作成
  2. 属性辞書を使ってクラスを作成

さて、もう大体わかったかと思いますが、クラス定義を書くと「クラスを定義するためのバイトコードが実行される」ということになります。

クラス定義コードを見てみる

というわけで先ほどの「中身の部分は?」を見てみましょう。クラス定義コードはいちばん外側のコードオブジェクトが定数として持っています。

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

つまり、co.co_consts[0]がクラス定義コードになります。

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

  3           8 LOAD_CONST               1 (<code object instance_method at 0x031C7860, file "<string>", line 3>)
             10 LOAD_CONST               2 ('Foo.instance_method')
             12 MAKE_FUNCTION            0
             14 STORE_NAME               3 (instance_method)

  6          16 LOAD_NAME                4 (classmethod)
             18 LOAD_CONST               3 (<code object class_method at 0x031C78B8, file "<string>", line 6>)
             20 LOAD_CONST               4 ('Foo.class_method')
             22 MAKE_FUNCTION            0
             24 CALL_FUNCTION            1
             26 STORE_NAME               5 (class_method)
             28 LOAD_CONST               5 (None)
             30 RETURN_VALUE

真ん中はインスタンスメソッドの定義。先ほど説明したように関数名(メソッド名)と関数本体をスタックに積んでMAKE_FUNCTIONで関数オブジェクトを作ってSTORE_NAMEで属性辞書に格納しています。しれっと「属性辞書」という単語を使ってますが詳しいことは割愛します。

さてようやくメインイベント、デコレータを使ったクラスメソッド定義に対応する部分です。改めて見てみましょう。

  6          16 LOAD_NAME                4 (classmethod)
             18 LOAD_CONST               3 (<code object class_method at 0x031C78B8, file "<string>", line 6>)
             20 LOAD_CONST               4 ('Foo.class_method')
             22 MAKE_FUNCTION            0
             24 CALL_FUNCTION            1
             26 STORE_NAME               5 (class_method)

CALL_FUNCTION直前のスタックは以下のようになっています。

class_method関数オブジェクト
classmethod関数オブジェクト

しまった、クラスメソッドの名前もう少しまじめにつければよかった(笑)
ともかく、できたてほやほやの「class_method関数オブジェクト」を引数にclassmethod関数が実行されます。その結果が「class_method」という名前で属性辞書に格納されます。

まとめ

Q:デコレータは関数が実行されるはずだが何故クラス定義中に書けるのか
A:クラス定義とは関数を実行することだから、関数の中で関数が呼び出されていてもなんら問題ない

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7