Python Class

クラス

分析が複雑になると、多くの関数を書く必要がでてきます。

ただし、多くの関数を書くと、様々な問題が出てきます。

長い関数を書くと、変更に弱くなります。

また、途中の計算結果のみを取得したい場合は、その後の無駄な計算をしてしまいます。

一方で、短い関数に分けて作ると、管理が難しくなります。

例えば、ある関数はデータ A に関する操作、ある関数はデータ B に関する操作だった場合、どちらのデータの関数かを覚えておかないといけません。

クラスを使えば、関数や変数をひとまとめにすることができ、そういった問題を解決することができます。

クラスは、class によって定義します。

[1]:
class First:

    pass

まず First というクラスを定義しました。

関数と同じように、クラスの内容をインデント・ブロック内に書いていきます。

pass は何もしないという意味です。

何も書かないとエラーになるので、pass を書いています。

次のようにクラスを呼び出してみます。

[2]:
First
[2]:
__main__.First
[3]:
First()
[3]:
<__main__.First at 0x118ddc4a8>

インスタンス

上の FirstFirst() はどういった違いがあるのかはわかりませんが、大きな違いがあります。

First はクラスを呼び出します。

First() はクラスのインスタンスを返します。

例えるならば、クラス自体は設計図のようなものです。

インスタンスは、その設計図をもとに作られた建造物のようなものです。

なぜ、この違いが重要なのかは後ほど説明します。

属性

さきほどのクラスは、特に変数も関数も持っていないので、変数を付与しましょう。

次のように、インデント・ブロック内に変数を定義します。

[4]:
class Second:

    a = 100 #クラス属性

クラスの中で a という変数を定義しました。クラスが持つ変数を 属性 と呼びます。

また、属性にはクラス属性とインスタンス属性があります。

インデント・ブロック内で定義した変数はクラス属性です。

インスタンス属性の作り方は後ほど説明します。

属性を参照するには、クラス名.属性名 とします。

[5]:
Second.a
[5]:
100

メソッド

今度は、関数を持たせましょう。

[6]:
class Third:

    def print_3(self):
        print(3)

クラスが持つ関数を メソッド と呼びます。メソッドの呼び出しは クラス名.メソッド名 とします。

[7]:
Third().print_3()
3

メソッドで注意しなければならないのは、メソッドでは自動的に第 1 引数としてインスタンス自身が代入されます。

Python では慣例的にこれを self としてます。

メソッドはクラスではなく、インスタンスでなければ使えないことに注意してください。

インスタンスを作ると、self が使えるようになります。

実際に、下のコードはエラーになります。

[8]:
# Third.print_3()

クラスの中のメソッドで違うメソッドを呼び出す場合、また属性を呼び出す場合は、self.メソッド名self.属性名 とすればできます。

こうすることによって、用途ごとに関数や変数をまとめ、内部で使い回すことができます。

[9]:
class Fourth:

    a = 100

    def print_a(self):
        print(self.a) # 属性 a を出力

    def call_print(self):
        self.print_a() # print_a メソッドの呼び出し

[10]:
Fourth().print_a()
Fourth().call_print()
100
100

コンストラクタ

実は、インスタンスを作る際に、コンストラクタというメソッドが呼び出されます。

Python では、__init__ というのがそれで、init の前にアンダースコアを2つ前後に配置します。

これらを特殊メソッドと呼び、クラスの振る舞いを変えることができます。

コンストラクタに追加の引数を定義することによって、元の設計図に変化をつけたインスタンスを作れます。

例えば、アンケートのデータ整理を行う、Survey というクラスを作ったとしましょう。

アンケートのデータ整理で行う操作はすべて共通ですが、いくつかの属性は異なるべきです(例えば、アンケートの実施年など)。

例えば、次のようにすれば、インスタンス属性を作ることができます。

[11]:
class Survey:

    some_attribute = 0
    a = 3

    def __init__(self, year):
        this_year = year
        self.year = year

    def print_a(self):
        print(self.a)

    def some_method(self):
        pass

[12]:
survey2018 = Survey(year = 2018)
survey2019 = Survey(year = 2019)
print(survey2018.year)
print(survey2019.year)
2018
2019

クラス属性では、変数を定義すればクラス属性になりましたが、インスタンス属性は self.属性名 としないとインスタンス属性になりません。

普通に変数を作成すると、ローカル変数になります。

実際、__init__ メソッドで this_year という変数を定義していますが、メソッド外からは参照できません。

[13]:
# survey2019.this_year

継承

ほとんど年のアンケートではデータ整理の操作は同じですが、ある年だけ異なる操作と属性が必要だったとしましょう。

継承 を使うことによって、属性とメソッドを引き継いで、新たにクラスを定義することができます。

クラスの定義の際に、クラス名(継承クラス名) とすることによって、継承を行うことができます。

[14]:
class SurveyExtra(Survey):

    b = 50 # 追加クラス属性

    def __init__(self, year, c):
        super().__init__(year) # Survey のコントラクタ
        self.c = c # 追加インスタンス属性

    def print_b(self): # 追加メソッド
        print(self.b)

新たに定義した SurveyExtra は、継承した Survey の属性とメソッドを持っています。まずはそれを確認してみます。

[15]:
Survey2009 = SurveyExtra(year = 2009, c = 10)
print(Survey2009.a)
Survey2009.print_a()
3
3

SurveyExtra で定義した属性とメソッドを呼び出します。

[16]:
print(Survey2009.b)
Survey2009.print_b()
50
50

継承したクラスと同じ名前のメソッドを定義すると、継承したクラスのメソッドは上書きされますが、super() を使えば継承したクラスを呼び出すことができます。

これによって、例えば Survey のインスタンス属性に新たにインスタンス属性を追加できます。

SurveyExtra のコントラクタを詳しく解説すると、 1. まず引数に Survey のコントラクタで必要だった year と、新たに b という引数を追加しています。 2. super().__init__(year) によって、Survey のコントラクタを実行しています。これによってインスタンス属性 year が付与されました。 3. 最後に、 self.c = c で新たなインスタンス属性を付与しました。