Py3k の super

super() について - methaneの日記 について

super() は、 super(Type, obj) とするとsuper型のオブジェクトを返し、そのオブジェクトには __getattr__() があって、 super(Type, obj).foo とすると、 mro の Type 以降で一番最初に foo を持っているクラスを探し、その foo を obj に bind して返す。

Python 3.0 からは、super() の引数が省略できるようになっている。これは、 super() の主な用途が、JavaRubyのsuperと同じく class の中で親クラスのメソッドを呼び出すものであり、毎回 super(ThisClass, self) を書くのは DRY 原則に反するからである。 *1

さて、僕が super() について - methaneの日記 で思いっきりバカなこと書いてた事の訂正をしておく。

super は self の mro から ひとつ手前の class を取り出す関数だから、 self と Type を知らないといけない。この二つを引数にとるのが自然。普通の関数の仕組みのままでできる。

super が self と Type を知らないといけないのは合っているが、それを省略できるようにすると「普通の関数の仕組み」の枠から外れるという前提が間違いで、後述する少しの修正とhackで実現可能だった。

まず、 self の省略から。これは簡単で、 super() を呼び出した関数の第一引数を取ってくれば良い。selfを s などと省略しても問題ない。スタックフレーム中の変数を参照する仕組みなら Python 2.x でもある。

次に、ThisClassの省略について。これには、 Python 3.0 で追加された __class__ という変数を利用している。

>>> class Foo(object):
...   def bar(self):
...     return __class__
... 
>>> f = Foo()
>>> f.bar()
<class '__main__.Foo'>
>>> import dis
>>> dis.dis(Foo)
Disassembly of bar:
  3           0 LOAD_DEREF               0 (__class__) 
              3 RETURN_VALUE         

class 内で def された関数を呼び出したときだけ、cellに __class__ という変数が入る。なので、引数を省略された super は呼出側関数のスタックフレームから __class__ を取り出して利用する。

ということで、 Python3.0 では、大きな変更無しに super() の引数省略を実現していた。僕が言った、

self と Type を省略できるようにしようとすると、通常の def とは違う、class 構文内でしか使えないメソッド定義専用の構文が必要になるし、superだけじゃなくてselfも用意しないといけない。class周りの構文がもう一セットできる訳だ。

は完全に間違いだった。

さて、 Python2.x の super() は、ダイヤモンド継承無しの場合はタイプ数が増えて実行速度が遅くなるだけで良い事無し、ダイヤモンド継承があってもメリットとデメリットを比較すると積極的に使いたくは無い機能だった。これが、 Python 3.x では状況が変わってくる。

まず、タイプ数が減った。 super() をタイプする代わりに、クラス名と self を書かなくてよくなったので、よほどクラス名が短くない限りは super() を使った方がタイプ数が少なくなる。

次に、グローバル変数*2からクラス名を使ってクラスをロードする必要がなくなった。Python 2.x では、自分のクラス名を書いても次のように LOAD_GLOBAL でクラスオブジェクトのロードをしているので、クラスを名前で引っ張ってこれなくなるとエラーになっていた。

>>> class Foo(object):
...     def __init__(self):
...         super(Foo, self).__init__(self)
... 
>>> dis(Foo)
Disassembly of __init__:
  3           0 LOAD_GLOBAL              0 (super)
              3 LOAD_GLOBAL              1 (Foo)
              6 LOAD_FAST                0 (self)
              9 CALL_FUNCTION            2
             12 LOAD_ATTR                2 (__init__)
             15 LOAD_FAST                0 (self)
             18 CALL_FUNCTION            1
             21 POP_TOP             
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE        

>>> Bar = Foo
>>> del Foo
>>> f = Bar()
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
  File "<ipython console>", line 3, in __init__
NameError: global name 'Foo' is not defined

Python 3.0 では、Cで書かれた super() の中で、 cell からの __class__ の取り出しや self の取り出しを行っているので、 LOAD_GLOBAL と LOAD_FAST が減って速くなるし、クラス名が変更されても問題ない。

>>> class Foo:
...   def __init__(self):
...     super().__init__()
... 
>>> from dis import dis
>>> dis(Foo)
Disassembly of __init__:
  3           0 LOAD_GLOBAL              0 (super) 
              3 CALL_FUNCTION            0 
              6 LOAD_ATTR                1 (__init__) 
              9 CALL_FUNCTION            0 
             12 POP_TOP              
             13 LOAD_CONST               0 (None) 
             16 RETURN_VALUE         

>>> Bar = Foo
>>> del Foo
>>> f = Bar()

特にタイプ数の削減がうれしい。 Python 3.0 からは super() を主に使っていこう。

*1:このあたりの論議は Python3000 の ML で一応見つけたけど、それより以前からあったのかもしれない。

*2:もちろん、Pythonグローバル変数と言ったらモジュール変数のこと

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。