今回は、これまで細かく説明してこなかったselfについて扱います。self自体については割りきってしまえば非常に単純明快なので、細かい話に興味がない人は最初の節のみ読んでいただければ大丈夫です。2節目以降は、かなり前の関数やモジュールの回で説明した「名前空間」と「スコープ」に関わる話となります。

メソッドのselfについて

まず前回までの復習をします。Pythonのクラスのメソッドやコンストラクタでは第一引数をselfとし、それらの定義されたメソッドを呼び出す際はselfに該当する引数を指定しないのでした。

たとえば、以下のクラスのコンストラクタ__init__とmethod1の利用方法を見ればわかりますね。宣言にはselfがありますが、呼び出しにはselfに相当するものがありません。

class MyClass:
    def __init__(self):
        print('constructer')

    def method1(self, a):
        print(a)

instance = MyClass()
instance.method1('hello')
# constructer
# hello

さて、前回まではこのselfについて詳細を伝えなかったのですが、今回はこれが何なのかということを確認してみます。

先ほどのクラスにselfのタイプとそれ自身(参照)を表示するメソッドを追加します。そして、それとは別にインスタンスのタイプと参照を表示します。

class MyClass:
    def method2(self):
        print('self type: ' + str(type(self)))
        print('self ref:  ' + str(self))

instance = MyClass()
print('instance type: ' + str(type(instance)))
print('instance ref:  ' + str(instance))
# instance type: <type 'instance'>
# instance ref:  <__main__.MyClass instance at 0x105043ea8>

instance.method2()
# self type: <type 'instance'>
# self ref:  <__main__.MyClass instance at 0x105043ea8>

表示を見てもらうとわかりますが、print(instance)で表示しているインスタンスと、メソッド内のprint(self)が表示している参照が同じなので、両者はまったく同じものであることがわかりますね。つまりメソッドの第一引数のselfには「インスタンスとしての自分自身」が入ることがわかります。

実際にprint(type(self))でselfが何なのかを確認してみるとinstanceとなっていますね。このことから、ちょうどインスタンスのデータ(フィールド、属性)を「インスタンス.データ」としてアクセスするように、メソッド内でも自分自身のデータを「self.データ」としてアクセスしていることがわかります。

Javaなどとの違い

Pythonのメソッドの「第一引数でselfを使う」というルールはJavaなどのほかのオブジェクト指向の文法とかなり違うので注意してください。selfはJavaのクラス内で利用されるthisに近いものですが、Javaではthisをメソッドの引数として宣言しません。thisはJavaの文法に存在する特別なキーワードだと思ってください。

一方、Pythonのメソッドは「第一引数に自分自身のインスタンスが入る」という特別なルールがあるものの、必ずしもその引数の変数名はselfである必要はありません。別にselfの箇所にくる変数名がaでもbでもかまわないのですが、「自分自身」ということを表明するために誰しもがselfという変数名を使います。

selfを使えと文法で決まっているわけではありませんが、selfは特別な意味を持って使われているため、selfを使うべき場面でほかの名前を使ったり、逆にほかの関係ない変数名でselfと名付けるのは避けるべきです。

なお、selfを書かずに変数などを利用すると、それはインスタンスが持つ変数ではなく、特定のメソッド内でのみ利用するローカル変数になります。別のメソッドからそれを利用しようとすると、それはエラーとなってしまいます。

class MyClass:
    def method1(self):
        a = 'test1'
        self.b = 'test2'

    def method2(self):
        #print(a)  Error
        #print(b)  Error
        print(self.b)

instance = MyClass()
instance.method1()
instance.method2()

いずれにせよ、Pythonのメソッドでは第一引数をselfとし、インスタンスのデータはself.変数、self.関数としてアクセスします。これだけは暗黙のルールであると思ってください。

グローバル変数と関数の変数のスコープの復習

グローバル変数と関数のローカル変数について思い出してみましょう。変数には属する「名前空間」があり、特に何もない箇所で作成した場合はglobal変数となり、関数で作成した場合は関数内でのみ通用する変数となるのでした。そして、関数内でglobal変数を利用する場合は「global 変数」として宣言が必要でした。このときglobal変数は「global の名前空間」にあり、関数の変数は「関数の名前空間」にあるといわれています。そして変数が見える範囲を「スコープ」と呼んでいます。

復習がてら、簡単な例を見てみましょう。

a = 'global var: a'
b = 'global var: b'

def test():
    a = 'local var: a'
    global b
    b = 'updated: b'
    c = 'local var: c'

上記を見てもらうとわかりますが、グローバル変数a、bを関数testで更新するというものです。テストをしてみます。

test()
print(a)
print(b)
# global var: a
# updated: b

print(c)
# Traceback (most recent call last):
#  File "/Users/yuichi/Desktop/test.py", line 21, in <module>
#    print(c)
# NameError: name 'c' is not defined

グローバル変数aは関数testの呼び出し内でaに値を代入されても、元の値をキープしています。これはグローバル変数のaと、関数test内の変数aが別物だからです。一方、変数bは関数内でglobal変数として扱っているため、関数の外側のbも更新されていますね。

test関数内で宣言した変数cを、関数外(グローバルの名前空間)で利用しようとしてエラーとなっていますが、これも変数aが新されていないのとまったく同じ理由です。cは関数内の変数であり、これは関数の外では利用できません。エラーの内容を読むと'Name Error'で'cは定義されてない'となっていることがわかりますね。

この名前空間のルールは以下のようになります。

名前空間のルール

関数はグローバル変数を参照可能ですが、グローバル変数は関数のローカル変数を参照できません。また、異なる関数同士も互いのローカル変数を参照できません。

クラスとインスタンスの名前空間

クラスとインスタンスにも名前空間があります。まず最初に図で簡単に説明してから、実例に入ります。

以下の図を見てください。

クラスとインスタンスの名前空間

先ほどの名前空間と異なり、今度は入れ子構造になっています。globalの名前空間内にクラスの名前空間、そしてインスタンスの名前空間が存在しています。クラスの名前空間はそのクラスのインスタンスすべてで共通して持っているデータがあり、それを「クラス変数」といいます。

たとえば、クラスAのインスタンス1でクラス変数の値を設定すると、同じクラスAの別のインスタンス2でも書きかえられた新しい値となります。

まぁ、うだうだ概念的な話をしていても眠くなりそうなので、コードで確認してみます。以下のコードを見てください。

a = 'global var'

class MyClass:

    b = 'class var'

    def __init__(self, c):
        self.c = c

instance1 = MyClass('instance var 1')
instance2 = MyClass('instance var 2')

変数a、cについて改めて説明する必要はありませんね。aはどこからでもアクセス可能なグローバル変数で、cはインスタンスが持つ変数です。

クラスの中でaを利用するには、参照だけであればglobal宣言は不要ですが、代入などの書き込み処理をする場合はglobal宣言が必要なので注意してください。

クラスの直下に作られている変数 b がクラス変数です。メソッドではなくクラス内で宣言しているだけなので、それほど難しくありませんね。

クラス変を利用するのも特に難しい点はなく、「クラス名.変数名」とすればアクセス可能です。

print(MyClass.b)
# class var

なお、実はインスタンス変数と同じように「インスタンス名.変数名」としてもクラス変数にアクセスできます。たとえばprint(instance1.b)としても"class var"と表示されます。

ただ、この挙動は正確には、

  • 参照(read): インスタンス変数があればそこから、なければクラス変数から取得
  • 代入(write): 新しいインスタンス変数を作って代入

として動いているので、インスタンスを使ったクラス変数へのアクセスは避けたほうが無難です。また、混乱を避けるためにも、クラス変数とインスタンス変数で同名の変数名は付けないことが望ましいです。

ちなみに、クラス内でのクラス変数の参照は、以下の2つのメソッド内の手法で実現します。

class MyClass:
    b = 'class var'

    def print_class_var1(self):
        print(type(MyClass))  # <type 'classobj'>
        print(MyClass.b)  # class var

    def print_class_var2(self):
        print(type(self.__class__))  # <type 'classobj'>
        print(self.__class__.b)  # class var

まず第一の方法は、クラス内で「クラス名.変数」としてしまう方法です。print_class_var1を見てもらうとわかります。

もうひとつの方法が「インスタンスからクラスを取得し、変数にアクセス」というものです。後者はクラス名の変更時などにクラス内の変更が不要というメリットがありますが、見た目がくどいので私はあまり使わないです。

オブジェクトへのデータの動的追加

今回最後の本節は、Pythonのオブジェクト指向が「どのように実現されているか」を少し深掘りしてみようという内容です。このレベルまで気にしなくてもプログラムは組めるため、プログラミング初心者のかたは読み飛ばしていただいていいと思います。

今までPythonのインスタンスはクラスから作られるという話を何度もしました。インスタンスで持つデータはクラスで定義するのでしたね。その定義の仕方をよく思い出してください。以下のようにコンストラクタでselfに追加していっていました。

class MyClass:
    def __init__(self):
        self.a = 'a'
        self.b = 'b'

今回お話したように、selfはインスタンス自身でしたね。コードを見てみてください。self.a = 'a'としていることからわかるように、そのインスタンスに変数を代入しているように見えます。ただ実際には、この動きはJavaやC++のクラスとかなり違うので注意が必要です。

実はこのself.a = 'a'が実施される直前までは、このインスタンスはaというデータを持っていません。そのため、self.a = 'a'はインスタンスがすでに持つ変数への値の代入というよりは、新しくデータを作っているようなイメージです。これは、JavaScriptなどで利用されているプロトタイプベースのオブジェクト指向に似ています。

少しコードを書き換えてテストします。

class MyClass:
    def __init__(self):

        print('1: ' + str(dir(self)))
        self.a = 'a'
        self.b = 'b'
        print('2: ' + str(dir(self)))

instance = MyClass()
print('3: ' + str(dir(instance)))

上記ではあるオブジェクトが持つ属性を確認するdir関数を使うことで、インスタンスが持つデータを確認しています。まず、コンストラクタ__init__でselfに代入する前後で、そのインスタンスがどのようなデータを持っているか表示します。そして、作成し終わったインスタンスの中身を確認しています。

実行結果は以下のようになります。

1: ['__doc__', '__init__', '__module__']
2: ['__doc__', '__init__', '__module__', 'a', 'b']
3: ['__doc__', '__init__', '__module__', 'a', 'b']

見てもらうとわかるようにすべて同じインスタンスであるにもかかわらず、持っている属性にa、bが途中で追加されていますね。Javaなどだとコンストラクタで属性の中身に変更を加えられますが、属性の有無そのものは変化しません。

実はこの属性の追加はコンストラクタ以外でも実行できます。たとえば、先ほど作ったMyClassのインスタンスにクラスで定義されていない属性cを追加してみます。

instance.c = 'c'
print('4: ' + str(dir(instance)))
# 4: ['__doc__', '__init__', '__module__', 'a', 'b', 'c']

dirで確認すると、今まで存在しなかった属性cがあることがわかります。Pythonではこのようなことができるということは、知っておくべきだと思います。ただ、コンストラクタ以外でのオブジェクトのデータの追加は、可能な限り控えるべきだと思います。インスタンスの属性があったりなかったりするとトラブルのもとですし、なによりクラスのコードだけを読んでも、インスタンスがどのようなデータを持っているのかわからなくなります。

必要な属性が増えたら、それは動的に追加するのではなく、コンストラクタの中に書く。使い捨てのコードならかまわないと思いますが、特に初心者のかたは見やすいコードを書くことに注意を払ってください。

クラスベースとプロトタイプベースのインスタンスの作られ方の違いについて、以下に概念図を記載します。

クラスベースとプロトタイプベースのインスタンスの作られ方の違い

プロトタイプベースのほうが融通が効きますが、規則性を持ってコードを書かないとカオスになるので注意してください。初心者のうちは厳格にルールを守ることに注力すべきだと思います。


演習1

クラスのコンストラクタやメソッドの第一引数がなぜselfであるか説明してください。また、selfの実体が何か説明してください。

演習2

以下について説明してください。

  • グローバル変数
  • 他のモジュールのグローバル変数を使う方法
  • 関数内のローカル変数
  • クラス変数
  • インスタンス変数

演習3

クラスベースのオブジェクト指向とプロトタイプベースのオブジェクト指向について調べてください。


次回は複数のクラスを連携させる際にどうするのか、どう設計するのかについて扱います。その次の回あたりで、今までの復習も兼ねてゲーム作りをしたいと考えています。

執筆者紹介

伊藤裕一(ITO Yuichi)

シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。

もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択

詳細(英語)はこちら