[Python] self が指しているもの

2020年10月31日

 Python小ネタシリーズです。Python でクラスを書くと大量に書くことになるself。このself が指しているものを正しく理解することが、クラスを使用する上でとても重要なのではないかと思います。

「self はクラスインスタンスを指している」ということの意味

 結論から言うとself はクラスインスタンスを指しています。ここではそれが意味するところを掘り下げてみたいと思います。

 今、仮にシンプルなクラスを作ってみます。

class MyBaseClass(object):
    def __init__(self):
        print(self.__class__)

これを使ってみます。

A = MyBaseClass()

object を継承したシンプルなMyBaseClass を定義しました。MyBaseClass はコンストラクタの中でself の__class__ をprint するという動作をしています。

Python インタプリタ内で上記のコードを実行すると、結果は

<class '__main__.MyBaseClass'>

 のようになります。これはモジュールではない__main__ ネームスペースにあるMyBaseClass ですよという意味です。

 クラス定義の中に書いたselfは、実行したコードでAに格納したMyBaseClass のインスタンスを指します。

 そんなことは当たり前だ、わざわざ説明するまでもない、ように見えますね。

 では次に、このMyBaseClass を継承したクラスを作ってみます。

class MyInheritedClass(MyBaseClass):
    def __init__(self):
        super(MyInheritedClass, self).__init__()

これも使ってみましょう。

B = MyInheritedClass()

すると結果は

<class '__main__.MyInheritedClass'>

 そんなことは当たり前…ですか?

 ここでどういうことが起きているか改めて見て見ます。

 Bという変数にMyInheritedClass のインスタンスを格納しました。MyInheritedClass はコンストラクタでスーパークラスであるMyBaseClass のコンストラクタを呼んでいます。そのためここでMyBaseClass で定義されている__init__()が実行されました。

 MyBaseClass の__init__() ではself.__class__print しています。

 つまり、MyBaseClass の定義に書いてあるselfが、ここではMyInheritedClass のインスタンスを指している、だからself.__class__<class '__main__.MyInheritedClass'> になるわけです。

self を使う時の注意

 self は主にクラス定義の中に書くことになりますが、クラスが継承される場合、問題になることがあります。

class MyClass(object):
    def __init__(self):
        super(self.__class__, self).__init__()

 例えばこのように、作成したクラスのコンストラクタでスーパークラスのコンストラクタを呼ぶとき、直接クラス名を書かずに、自分自身のクラスを意図してself.__class__ と書く方法があります。

 これはこのMyClass を直接呼び出している限りは問題ありません。

 しかし、これを継承すると問題が生じます。

class MySubClass(MyClass):
    def __init__(self):
        super(MySubClass, self).__init__()

ごくシンプルです。MySubClass はMyClass を継承してスーパークラスのコンストラクタを呼んでいます。

しかしこのMySubClass のインスタンスを初期化するとエラーになります。

A = MySubClass()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 3, in __init__
  [Previous line repeated 995 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object

 再帰エラーです。
 何が起きたのかというと、MySubClass を初期化しようとしてMySubClass の__init__() が呼ばれます。この__init__()はMySubClass のスーパークラスの__init__()を実行しようとします。

 そしてMyClass の__init__() が呼ばれますが、この__init__()は自分のクラスのスーパークラスの__init__() を呼び出します。

 この「自分のクラス」がクセモノということになるわけです。

 ここでself は初期化しようとしているインスタンスが入っているので、そのクラスはMySubClass です。つまり、MyClass の__init__()がMySubClass の__init__()を実行しようとするわけですね。

 するとループが生じます。MySubClass の__init__()がまたMySubClassの__init__()を呼ぶという事態が発生し、延々ループしてしまうわけです。

 上記の出力では995回を超える実行が行われ、最大再帰回数を超えたぞ、というエラーになって停止しました。

 つまりこのようにそのクラスが継承される場合、self.__class__ の使用には注意が必要です。

まとめ

  • self は生成されたクラスインスタンスを指す
  • そのためself に格納されているのは最終的に呼び出されたクラスのインスタンスである
  • 継承ツリーの上位になるクラスでのself の扱いには注意が必要