Python

Pythonにおけるclass文について解説

公開日:2019/10/15
更新日:2019/10/15
キーワード:Python class文
文字数:8600(読み終わるまでおよそ14分)

この記事でわかること

  • クラスとは
  • クラスに関する基本文法

はじめに

今回は、class文を見ていきます。

Pythonは、オブジェクト指向と呼ばれる方法論でプログラミングするための言語の一つです。

クラスの考え方は難しいですが、オブジェクト指向の世界でやっていくには避けて通れないので、是非マスターしてください。

1. クラスとは

ハイシィさんによる写真ACからの写真

オブジェクト指向を一言で説明するのは大変難しいのですが、あえて簡単に書くと、「クラスを最小の単位として、複数のオブジェクトを組合せてプログラムを構築する方法」と言えます。

オブジェクトとはデータ(属性)とメソッドを持ったもので、データは変数で保持し、メソッドは関数で定義されます。

Pythonでは、数値、文字列、リスト、タプルなどのデータを扱いますが、これらはすべてクラスから作られたオブジェクトです。つまりクラスとは、どのようなオブジェクトを作るか、を示したオブジェクトの設計図と言えます。そして、クラスから作ったオブジェクトのことを、インスタンスと言います(図1)。

図1. クラスとインスタンス

Pythonでのオブジェクト指向プログラミングにおいて、クラスの考え方を理解することは非常に重要であることを、ご理解いただけると思います。

さて、オブジェクト指向の世界では、基本的にすべてのデータや関数は、何らかのクラスに属している必要があります。

例えば、「place」という変数を宣言した場合、このplaceは何の場所なのかを定義する必要があります。自宅なのか、勤務先なのかといったようなことです。

そして、持っている共通の属性を、変数と可能な処理(関数)を機能として抽出し、その情報の持ち主を抽象化して名前をつけたものがクラスとなります(図2)。

図2. クラスの概念

ここでは、クラスは情報の持ち主であり、関数と変数の2つの情報を持っているということを覚えておいてください。

クラスの考え方は難しいので、もう少し身近な例でクラスのイメージを示すと、図3のようになります。

図3. クラスの簡単なイメージ

乗用車には色々な種類がありますが、これらに共通している属性を抽出して抽象化し、クラスとして定義しています。

図2の右側には色々な乗用車があります。車名、色、ドア数など各々は異なっていますが、「乗用車」という視点で見ると、すべてが乗用車と言えます。右側の絵はすべて乗用車なので、抽象化してCarというクラスで定義しています。

さて、このクラスは、intクラス、strクラスなどデフォルトで定義されているものもありますが、プログラミングによりユーザーが自ら定義することもできます。

それでは、具体的なコードを通して、クラスをもう少し詳しく見ていきましょう。

2. クラスに関する基本的な文法

はむぱんさんによる写真ACからの写真

1) クラスの定義

クラスを定義するコードは、クラス名を指定する1行と詳細を具体的に記述したステートメントで構成されています。

class クラス名 :
    ステートメント

具体的には例文1-1のように書きます。これはCarクラスを定義したコードです。必ずステートメントを書く必要があるため、ここではとりあえず、何も実行しないpassを書いています。

<例文1-1>

class Car:
    pass

クラス名を命名する際は、「MyFavoriteClass」のようにCapWords方式(単語の頭文字を大文字にしてつなげる)にするのが一般的です。

では、いま作ったCarクラスからインスタンスを作ってみましょう。(例文1-2)

<例文1-2>

car_a = Car()
car_b = Car()

新しく作ったクラスからインスタンスを作るには、クラス名に()をつけて呼び出すだけです(図4)。

図4. クラスからインスタンスを作るイメージ

今回の場合、Car()と書くだけで、何個でもCarクラスのインスタンスを作ることができます。車名、色、排気量などが異なる様々な乗用車を作ることができるのと、全く同じです。

新たにCarクラスを定義しましたが、ステートメントがpassと中身がない状態なので、クラスの中身を書いてみます。

クラスはオブジェクトの設計図ですので、オブジェクトの仕様をクラスに記述します。具体的には、「オブジェクトにどのような属性の値を持たせ、何をできるようにするか」を書くことになります。プログラミングでは、属性は変数で定義し、できることはメソッド(関数)で定義することになります(図5)。

図5. クラスに仕様を書くイメージ

先ほど作成したCarクラスについて、オブジェクトの仕様を定義します。変数とメソッドとして、以下を設定することとしましょう。

変数
kilo走行距離 (初期値 0)
color色 (初期値 ”black”)
メソッド機能
run引数の距離だけ走行する

クラスを定義する際は、インスタンスを作ったときに自動的に実行される初期化メソッドを書くことができます。

初期化メソッドとは、インスタンスが作られたときに、自動的に実行されるメソッドです。

通常インスタンスを作成する際は、変数は特定の初期状態を取っていてほしいことが多いので、初期化メソッドを使う機会は多いのではないかと思います。

初期化メソッドはコンストラクタとも呼ばれ、Pythonではinitメソッドで記述します。クラスの初期化メソッドでは、__init__のようにinitの前後に、アンダースコアを2つ付けるようにします。

def __init__(self, 引数2, 引数3, …):

Pythonでは、__(アンダースコア2つ)から始まる変数やメソッドは、そのクラス内でしか参照できないようになっています。

初期化する際、他のクラスからの影響を受けたり、影響を与えたりしたら困るので、クラス内限定となるような配慮がなされています。

__init__メソッドの引数1は、selfにするのが慣例です。引数2、3…は、初期化処理で利用します。

インスタンス変数は、初期化メソッドで初期化します。初期化メソッドの引数1で受け取るselfを参照して、初期化します。

self. 変数名 = 初期値

では、先ほど作ったCarクラスについて、engineとkiloの2つのインスタンス変数を初期化してみましょう(例文1-3)。

<例文1-3>

class Car:
    def __init__(self, color = "black"):
        self.kilo = 0 #初期値を0とする
        self.color = color #引数で受け取った値を初期値とする

これで、2つのインスタンス変数kiloとcolorの初期化ができました。初期値として、kiloには0が、colorにはblackが設定されます。

ここで、__init__メソッドの引数2にcolorの初期値を与え、kiloの初期値はその下の行で与えています。この違いが何かが分かりにくいですが、それは後ほど例を示して説明します。

次に、Carクラスのインスタンスを作ってみましょう(例文1-4)。インスタンスの作り方は、例文1-2と同じですが、__init__の引数2に初期値を与える効果を確認してみます。

<例文1-4>

car_a = Car()
car_b = Car("white")

car_aは、Carクラスの定義で設定した初期値で与えられています。一方、car_bのcolorの初期値はwhiteで与えられます。インスタンス変数の値は、「インスタンス.変数名」で確認できるので、それぞれのインスタンス変数を確認してみましょう(例文1-5)。

インスタンス. 変数名

<例文1-5>

print(car_a.kilo)
print(car_a.color)

print(car_b.kilo)
print(car_b.color)
0
black
0
white

car_aのインスタンス変数の値はCarクラスの定義で設定した値と同じになります。一方、car_bについては、kiloは定義通りですが、colorは引数で与えたwhiteになっていることが分かります。

carクラスはオブジェクトが持つ変数を指定しているのみで、その値は各インスタンスで保持されます(図6)。

図6. インスタンス変数のイメージ

ここで、__init__メソッドの引数で初期値を与えるメリットを、少し丁寧に見ていきます。

初期値を__init__メソッドの引数ではなく、kiloと同じく( )の外で設定することもできます(例文1-6)。

<例文1-6>

class Car:
    def __init__(self):
        self.kilo = 0 #初期値は0とする
        self.color = "black" #初期値は"black"とする

car_c = Car()
print(car_c.kilo)
print(car_c.color)
0
black

しかし、インスタンスを作る際、colorを”white”に設定しておきたいと思ったときに、例文1-4のように書くとエラーになってしまいます(例文1-7)。

<例文1-7>

car_d = Car("white")
TypeError                                 Traceback (most recent call last)
<ipython-input-42-736037ea248c> in <module>()
----> 1 car_d = Car("white")

TypeError: __init__() takes 1 positional argument but 2 were given

これは、__init__メソッドで引数に設定していないので、インスタンスを作るときに引数で指定できないためです。

もし、colorをwhiteに変更してインスタンスを作りたいと思ったら、一度blackのインスタンスを作ってから、colorを変更するコードを別に書いて更新する必要があります(例文1-8)。

<例文1-8>

car_c.color = "white"
print(car_c.color)
white

値を変更したいインスタンス変数が1つであればこれでも何とかなりますが、たくさんある場合は、__init__メソッドの引数で設定した方が簡単に設定できます。例えば、例文1-9のようにkiloもCar()の引数に書けば、インスタンス作成時にkiloの初期値を変更できます。

また、引数内の変数が多い場合は、変数名を明示した方が間違いがないでしょう。

<例文1-9>

class Car:
    def __init__(self, kilo = 0, color = "black"):
        self.kilo = kilo #引数で受け取った値を初期値とする
        self.color = color #引数で受け取った値を初期値とする

car_d = Car(15, "silver")

print(car_d.kilo)
print(car_d.color)

#引数内の変数が多い場合は、変数名を明示した方が分かりやすい
car_d = Car(kilo = 15, color = "silver")
15
silver

さて、これまでクラスのインスタンス変数の設定について述べてきましたが、クラスのインスタンスが実行できることをインスタンスメソッドで定義することができます。

インスタンスメソッドの定義方法は、関数の定義方法とほとんど同じです。関数の定義と違う点は、初期化メソッドと同じく第1引数にselfを設定することです。

def メソッド名(self, 引数2, 引数3,…) :
    ステートメント

それでは、先ほど作ったCarクラスに、インスタントメソッドrun()を定義してみます。run()メソッドの機能は、引数で受け取った距離だけ走行距離が増え、総走行距離を表示する機能としましょう(例文1-10)。

<例文1-10>

class Car: #Carクラス定義
    
    #初期化メソッド
    
    def __init__(self, color = "black"):
        self.kilo = 0 #初期値は0とする
        self.color = color #引数で受け取った値を初期値とする
    
    #インスタンスメソッド定義

    def run(self, dist):
        self.kilo = self.kilo + dist
        comment = f"今回の走行距離は{dist}kmで、総走行距離は{self.kilo}kmとなりました。"
        print(comment)

car_a = Car() #car_aインスタンス作成
car_a.run(100) #car_aに対してrun()メソッドを実行
car_a.run(150) #car_aに対してrun()メソッドを実行
今回の走行距離は100kmで、総走行距離は100kmとなりました。
今回の走行距離は150kmで、総走行距離は250kmとなりました。⇒総走行距離が足されています

これまで、インスタンスに適用される変数とメソッドの定義方法を見てきましたが、クラス全体に適用される変数とメソッドも定義することができます。これらをそれぞれ、クラス変数クラスメソッドと言います。

クラス変数の値は、各インスタンスで共有して使用でき、クラスメソッドも各インスタンスから実行できます。

クラス変数は、通常の変数と同じように初期化します。

クラス変数 = 初期値

先ほど作ったCarクラス(例文1-10)に、2つのクラス変数を追加してみます(例文1-11)。

<例文1-11>

class Car: #Carクラス定義
    
    #クラス変数

    daisu = 0 #生産台数
    manufac = "DOYOTA" #メーカー名
   
    #初期化メソッド
    
    def __init__(self, color = "black"):
        self.kilo = 0 #初期値は0とする
        self.color = color #引数で受け取った値を初期値とする
    
    #インスタンスメソッド定義

    def run(self, dist):
        self.kilo = self.kilo + dist
        comment = f"今回の走行距離は{dist}kmで、総走行距離は{self.kilo}kmとなりました。"
        print(comment)

クラス変数を参照するには、インスタンスを作る必要はなく、「クラス.クラス変数名」で参照できます(例文1-12)。クラス変数を設定できていることが分かります。

<例文1-12>

print(Car.daisu)
print(Car.manufac)
0
DOYOTA

続いて、クラスメソッドを定義します。クラスメソッドは通常の関数と同様にdef文で定義しますが、関数の定義と異なるのは第1引数をclsにすることと、def文の前に@classmethodと書くことです。

@classmethod
def クラスメソッド名(cls, 引数2, 引数3, …) :
    ステートメント

具体的に見るため、例文1-11にクラスメソッドを追加し、インスタンス変数初期化メソッドも少し修正してみます(例文1-12)。新しくインスタンスを作るたびに、生産台数が1台ずつ増えるプログラムです。

<例文1-12>

class Car: #Carクラス定義
    
    #クラス変数

    daisu = 0 #生産台数
    manufac = "DOYOTA" #メーカー名

    #クラスメソッド

    @classmethod
    def count(cls):
        cls.daisu += 1
        print(f"生産台数は、{cls.daisu}台です。")

    #初期化メソッド
    
    def __init__(self, color = "black"):
        Car.count() #クラスメソッドを実行する。インスタンスを作ったときに生産台数を1台追加する。
        self.number = Car.daisu #クラス変数を参照する。自分の番号。
        self.kilo = 0 #初期値は0とする
        self.color = color #引数で受け取った値を初期値とする
    
car_a = Car()
car_b = Car("white")
car_c = Car("red")
生産台数は、1台です。
生産台数は、2台です。
生産台数は、3台です。

インスタンスを3つ(car_a~car_c)作りましたが、生産台数が1台ずつ増えており、初期化メソッドにあるクラスメソッドのcount()が実行されていることを確認できました。

2) クラスの継承

ここでは、クラスの継承について見ていきます。クラスを利用するオブジェクト指向プログラミングにおいては、このクラスの継承が利点の一つです。

クラスの継承とは、既存のクラスを拡張して新しいクラスを定義する方法です。例えば、Aというクラスがあったとしましょう。Aの機能を少し変更したBというクラスを作りたい場合、Aを継承して変更したい機能のみをBで定義することができ、これをクラスの継承と言います。

このとき、Bの影響はAに及ばないので、安心して継承できるのです。

AとBの呼び方は色々ありますが、「基底クラス/派生クラス」「親クラス/子クラス」「スーパークラス/サブクラス」などと呼ばれることが多いようです。

ここでは、公式ドキュメントの日本語訳で使われている、「基底クラス/派生クラス」を使うことにします。

派生クラスは自分の基底クラスを知っていますが、基底クラスはどのクラスが自分の派生クラスなのかは知りません。

基底クラスを持つ派生クラスを定義するには、クラス名の引数に基底クラス名を書きます。

class 派生クラス名(基底クラス名) :
    ステートメント

では、クラスの継承の具体例を見ていきます。インスタントメソッドcar_comment()を持ったCarクラスを定義します(例文2-1)。

<例文2-1>

car class Car:
    def car_comment(self):
        print("これは車です。")

次にCarクラスを継承したTruckクラスを定義します。Truckクラスには、インスタントメソッドtruck_comment()を定義しておきます(例文2-2)。

<例文2-2>

class Truck(Car):
    def truck_comment(self):
        print("これはトラックです。")

クラスが定義できたので、Truckクラスのオブジェクトautomobileを作ります。すると、Truckクラスで定義したメソッドtruck_comment()を実行できるだけでなく、Carクラスで定義したメソッドcar_comment()も実行できることが分かります(例文2-3)。

<例文2-3>

automobile = Truck()

automobile.truck_comment()
automobile.car_comment()
これはトラックです。
これは車です。

3) オーバーライド

基底クラスから派生クラスを作ったとき、派生クラスは基底クラスのメソッドを継承しますが、基底クラスのメソッドを変更したい場合は、派生クラスで同名のメソッドを定義することで、上書きすることができます。これを、メソッドのオーバーライドと言います。

例文3-1はオーバーライドの例です。makerメソッドをオーバーライドし、引数があれば派生クラスのメソッドを、引数が無ければ基底クラスのメソッドを実行します。基底クラスは、super()で参照できます。

<例文3-1>

#基底クラス
class CarD: #CarDクラス定義
       
    #クラスメソッド定義

    def maker(self):
        print("これは、DOYOTAの車です。")

#派生クラス

class CarB(CarD): #CarDの派生クラスCarB定義

    #基底クラスのメソッドをオーバーライドする

    def maker(self, name = None): #CarDのmaker()をオーバーライド
        if name:
            print("これは、" + name + "の車です。")

        else:
            super().maker() #引数がなければ、基底クラスのmaker()をそのまま使用

car = CarB()       #インスタンスを作成
car.maker("BONDA") #引数があるときは、派生クラスのmaker()を実行
car.maker()        #引数がないときは、基底クラスのmaker()を実行
これは、BONDAの車です。
これは、DOYOTAの車です。

CarBクラスはCarDクラスを継承していますが、CarDクラスと同じメソッドを呼び出してもオーバーライドすれば、CarDクラスとは違う結果を得られることが分かります。

3. おわりに

今回は、クラスの紹介をしました。

オブジェクト指向のプログラミングにおいて、読みやすく、間違えにくいコードを書くためには、クラスの考え方はとても重要です。

少し難しい考えですが、Pythonでのプログラミングに本格的に取り組みたい方は、避けて通れないので、確実に習得してください。

4. 参考サイト

python 入門 クラス
https://qiita.com/Lou-vtu/items/85275bc6477460a8494e

オブジェクト指向とクラス
https://docs.pyq.jp/python/library/class.html

公式ドキュメント 9. クラス
https://docs.python.org/ja/3/tutorial/classes.html#inheritance

【Python入門】クラスの継承、メソッドのオーバーライドとsuper
https://code-graffiti.com/class-inheritance-in-python/#toc2

ABOUT ME
北爪 聖也
ダメ営業マンからデータサイエンティストへキャリアチェンジ。 技術とビジネスサイドの橋渡しが出来るため、ダメ営業マンの経験も役に立ちました。 広告代理店ADKにて3年勤務→データ分析受託の会社DATUM STUDIOにて1.2年勤務後、独立。
データサイエンスの仕事に興味がある方!

株式会社piponではデータサイエンティストの方々へお仕事をご紹介しております。

もし、仕事にご興味ある方がいらっしゃいましたらこちらのフォームよりご連絡頂けると嬉しいです。

また、「未経験から転職して1年で身につけたスキルで月収25万円の副業を得るまで」という記事を有料noteとして出していますので、興味ある方は購入いただけると嬉しいです!