TL;DR
今回は、クラスとは何か分かりやすく説明したい。Python に限らず、クラスがどういったものか理解できなかった方も分かるかと思う。
プログラミングにおいて、クラスは「設計図」と例えられることが多い。その設計図を実体化したものがインスタンスと呼ばれる。それでは概念から説明しよう。
クラスのポイント
- オブジェクトの内容を定めた定義
- 複雑なデータや機能をまとめる
- 継承:親となるクラスから要素を受け継ぐことができる
- 初期化用メソッド
__init__
を記述する - 仮引数名には慣例として self という名前をつける
クラスとは?
はじめに、クラスを理解するうえで必須となる言葉をぼんやりと説明すると以下である。詳しくは後述する。
- オブジェクト:データ、機能。変数に代入できる。
- クラス:設計図。オブジェクトの内容を定めた定義。
- インスタンス:設計図をもとに作ったオブジェクト
- インスタンス変数:オブジェクトが持つデータ
- メソッド:クラスに紐づく処理
よくある例えでは、クラスは「たいやき器」であり、インスタンスは「たいやき」と表現される。作られた「たいやき」は、同じ性質を持つが、それぞれが独立している。
クラスは親となるクラスから要素を受け継ぐことができる。これを「継承」という。親の遺伝情報を、子供が受け継ぐイメージである。それだけでなく、子供は親を何人も自由に選べる上に、親から授かった機能を上書きできる。この上書きのことを「オーバーライド」という。
つまり、クラスとは「好きな情報を引き継ぎ、上書きも可能な設計図」と言える。
なぜクラスが必要なのか?
そもそも、なぜクラスが必要なのか?どんな恩恵があるのか?
基本的なデータであれば、組み込み型のオブジェクトで問題ないかもしれない。しかし、複雑なデータを扱うようになると、データや機能をまとめたくなる。このまとめた定義がクラスなのである。
小規模な開発においてはクラスの恩恵を感じられないが、似たような動作が多くなる大規模なソフトウェア構築においては、混乱を避け処理を効率的に使い回すために必要になってくる。理由をまとめると以下になる。
- 似たようなコードを抽象化して使い回せるようにするため
- 修正箇所を減らすため
Python のクラス
Python においてクラスとは、オブジェクトの種類を意味しており、オブジェクトの型を決める機能である。
そもそも「オブジェクト」という言葉が、ふんわりとし過ぎて分かりにくいという方もいるだろう。オブジェクトとは、データと機能がひとまとまりになったもので、変数に代入することができる。Python においては、str や int 、dict も全て組み込み型のオブジェクトである。
オブジェクトが持つデータのことをインスタンス変数と呼ぶ。
新しいクラスを定義すると、そのクラスに定義するオブジェクトを作ることができる。クラスの継承やメソッドの上書きなどの機能が備わっている。クラスは以下のようなもので構成されている
- クラス名
- 基底クラス名
- メソッド
基底クラス(継承元)を指定すると、基底クラスの性質を「継承」してサブクラスを定義できる。基底クラスを省略すると object クラスを継承する。
ルール
クラス名は、頭文字を大文字にする。基底クラスはクラス名の横に()をつけて書く。クラスの定義の左横にはインデントをつける。
class クラス名:
クラスの定義
クラスの中に記述された関数をメソッドという。メソッドは必ず一つ以上の引数を持つ。メソッドはいくつも定義することができる。メソッドを記述する場合は関数と同様に def 文をつける。
通常、クラスには __init__ という初期化用メソッドを記述する。
初期化については後述する。
self はインスタンス自身(クラス自身)を表す。メソッド定義において、その最初の引数には慣例として self という名前を付けるが、self でなくても構わない。しかし、self にしておくと保守性が高くなる。self についてはこの後、後述するのでひとまず読み進めていただきたい。
class クラス名(基底クラス):
def メソッド(self, 引数1, 引数2):
オブジェクトは以下のように作る。
hoge = クラス名()
インスタンス
インスタンスは設計図をもとに作ったモノだと説明した。インスタンスを作ることで、クラスの中の関数を呼び出して処理させることができる。インスタンスは、メソッドや変数を持つ。クラスオブジェクトに()をつけて呼び出すとインスタンスを作ることができる。このとき、クラスオブジェクトに渡された引数は初期化される。
class Menu: # クラスを定義する
pass # 処理
x = Menu()
x.name = 'hello'
x.name
# 'hello'
インスタンスが持つメソッドを、インスタンスメソッドと呼ぶ。インスタンスメソッドでは、処理の中でインスタンス自身にアクセスできる。
初期化
クラスの初期化について説明する。
インスタンスが生成されるときにデータを初期化することができるので、C++ などのコンストラクタと似ているが、Python の init はインスタンスが生成されたあとに呼び出されるから、コンストラクタではなく「イニシャライザ」と呼ばれる。
イニシャライザとは、オブジェクトが作られたときに自動的に呼び出される関数である。オブジェクトに属性が与えられる。
イニシャライザは以下のように init をアンダースコアで挟んで記述する。第一引数は self をつける。
def __init__(self, 引数2): # イニシャライザ
self.属性名 = __init__ メソッドの引数
# 引数は __init()__ に渡され初期化される
class Menu:
def __init__(self): # イニシャライザ
print('最初に表示される')
x = Menu()
# 最初に表示される
self
インスタンスメソッドの第一引数には必ずインスタンス自身のオブジェクトが渡される。仮引数名には慣例として self という名前をつける。self はインスタンス自身(クラス自身)を表す。self が存在することでクラスの変数だということを明示できる。オブジェクトの属性は「self.属性名」という式で参照される。
# self.属性名 = __init__ メソッドの引数を表す
self.n = n
self の横についている名前と右辺の名前が同じなので非常に紛らわしいと思う方もいるだろう。ざっくり言うと左辺が属性で、右辺が引数である。属性とはオブジェクト内の変数である。
> self.n = n のうち、self. の次の n は属性を表し、
右辺の n は、__init__ メソッドの引数を表していますので、
混同しないようにしてください。
self.属性名を代入文の左辺に書けば、属性をセットすることができる。
class Dog:
def __init__(self, 引数1, 引数2):
self.name = 引数1
self.weight = 引数2
pochi = Dog("ポチ", 10)
print(f'{pochi.name}の体重は{pochi.weight}kgです。')
# ポチの体重は10kgです。
クラス変数
クラス変数はクラスオブジェクトに紐づく変数。クラスの全てのインスタンスで同じ変数が共有される。クラスオブジェクトやインスタンスからもクラス変数を参照できる。クラス定義のトップレベルに変数を定義する。以下は、クラス変数を参照している。
# クラス変数
class Hoge:
a = 'Hello'
Hoge.a
# 'Hello'
クラス変数は、インスタンスからも参照可能である。
class Hoge:
a = "Hello"
h = Hoge.a
h
クラスメソッド
クラスメソッドはクラスに紐づくメソッドで、第一引数にクラスオブジェクトが渡される。メソッドの一番上に @classmethod をつける。第一引数は、self でなく cls とする。
class Hoge:
@classmethod
def a(cls):
print("これはクラスメソッドです。")
Hoge.a()
# これはクラスメソッドです。
クラスの継承
基底クラスとは親クラスのことである。クラス名の後に基底クラス名をつけると、基底クラスの性質を継承した子クラスを定義できる。継承により、変更部分だけを与えることにより、新しいクラスを定義することができる。
基底クラスのメソッドを呼び出すときは、組み込み関数である super() を利用する。最初から存在する組み込み型も上書きできる。
オーバーライド
サブクラスで基底クラスのメソッドと同名のメソッドを定義すると、メソッドを上書きできる。
# 基底クラス(継承元)
class Parent:
def __init__(self, a, b):
self.a = a
self.b = b
def w(self):
return self.b
# 継承先
class Child(Parent):
def w(self): # メソッドのオーバーライド
return super().w()
child = Child(0, 'hello')
child.w()
# 'hello'
# 基底クラス(継承元)
class Parent:
def __init__(self, name, age):
self.name = name
self.age = age
def my_name(self):
print("名前は" + self.name + "。年齢は" + str(self.age) + "歳。")
# 継承先
class Child(Parent):
def __init__(self, name, age):
super().__init__(name, age) # オーバーライド
def my_hello(self):
print("こんにちは")
# インスタンス化
yamada = Child("山田", 20)
yamada.my_name()
yamada.my_hello()
# 名前は山田。年齢は20歳。
# こんにちは
多重継承
基底クラスに複数のクラスを指定する。基底クラスをカンマ区切りで並べると多重継承になる。
class A:
def __init__(self, name):
self.name = name
def method(self):
print('A')
return self.name
class B:
def __init__(self, name):
self.name = name
def method(self):
print('B')
return self.name
class C(A, B):
pass
c = C('ccc')
c.method()
# A
# ccc
ひし形継承問題
以下のように複数の継承元が同じメソッド名にすると競合が起きる。無謀な継承は避けるべきである。
class A:
def hello(self):
print('hello A')
class B(A):
def hello(self):
print('hello B')
super().hello()
class C(A):
def hello(self):
print('hello C')
super().hello()
class D(B, C):
def hello(self):
print('hello D')
super().hello()
d = D()
d.hello()
# hello D
# hello B
# hello C
# hello A
以上、Python のクラスについて解説した。複雑なデータを扱うようになると、データと機能をまとめたオブジェクトを作りたくなるかと思う。ぜひ、この定義(クラス)を覚えておきたい。