python♪基本:「クラス」って必要?def関数でいいんじゃない?

そもそも、「クラス」って、なんのためにあるのでしょう。python関連の書籍では、「設計図」と表現されることが多いですが「???」です。「設計図」という表現はクラスの機能をうまく例えていますが、クラスを使う優位性はわかりません。そこで、この記事では、クラスを最初に勉強する人のためにクラスを使う意義を説明します。

0.チュートリアル学習のポイントシリーズ

この記事は「チュートリアル学習のポイントシリーズ」の記事です。一連の記事のリンクは、以下を参照してください。
Pythonチュートリアルの学習のポイントを整理

1. クラスは大規模なプログラム開発で威力を発揮する

クラスの勉強をしたときに、「別にdef関数でいいんじゃないの?」と思いませんでしたか?

でも、大規模なプログラム開発を行うためには、他の人が記述したコードを利用し、それらを組み合わせて目的のプログラムをコーディングしていきます。もし、他の人の長~い、複雑なコードについて、その計算内容を完全に理解しなければならないのであれば、手間がかかって、やってられません。
そこで、「オブジェクト指向」という大規模なプログラム開発を効率的に行うことを目的とした考え方が生み出されました。

クラスという仕組みはオブジェクト指向の考え方を実現するための仕組みで、「読みやすい」「最小限の使い方を知るだけで、コードの詳細を知らなくても使える」「部分的に改良して使える」といった特徴があります。

なお、Pythonにおける具体的な解決手段として、 主に以下の①②があります。この記事では、①の「カプセル化」についてのみ説明します。まずは、それを知ることで、クラスの雰囲気をつかむことができると思います。
①「データ」と「データを操作する方法(メソッド)」をひとまとまりに「カプセル化」した「オブジェクト」利用する 。
②「継承」「オーバーライド」という仕組みにより、元のプログラムを拡張したり、一部を改変したりすることができる。

2. クラスは「データ」と「データを操作する方法」をまとめる

ちょっと、難しい話になりそうで、いやな予感がしている方もいると思いますが、そうでもありません。1.で述べた「カプセル化」とは、「データ」だけでなく、「データを操作する方法」も、ひとまとまりにした「オブジェクト」というものを作って、変数に放り込んでしまおうという考え方です。

「オブジェクト」という言葉を使うと急に難しそうに思えますが、細かい定義は考えないで、オブジェクトは「データや関数をひとまとまりにしたもの」という程度にとらえれば、読みやすいと思います。

以下、クラスを使わないコードを、段階的にクラスを使ったコードに変化させて、クラスを使う意義を説明したいと思います。

(1) 分かりにくいコードの例(第1段階)

コード01は、クラスを使わない単純な例です。短いコードなので、太郎君と花子さんの平均点を出力するプログラムであることは十分にわかります。でも、この調子で1000行をこえるような複雑なプログラムを記述したらどうなるでしょう。変数名は意味の分からないaとかbとかですし、コードを記述した人でさえ、しばらくすると「なんだったっけ」となるのではないでしょうか。これでは、使い捨てのコードにしかなりません。

#コード01
a = '太郎'
b = 50
c = 45
d = (b + c) / 2

e = '花子'
f = 90
g = 85
h = (f + g) / 2

print(a, 'の平均点:', d)
print(e, 'の平均点:', h)
#出力01
太郎 の平均点: 47.5
花子 の平均点: 87.5

(2) 変数名を変えてみた(第2段階)

次は、変数名を変えてみました。なお、出力結果は出力01と同じです。

コード01とコード02は、変数名が違うだけですが、コード02の方が、あとで読み返したときにプログラムの意味が分かりやすいのではないでしょうか。変数名の記述は長くなっていますが、わかりやすくなります。つまり、「短いコード」=「他の人が読みやすいコード」ではないのです。

ただし、変数への名前はどんな名前でも自由につけられますので、名前の付け方を統一された表現にすることは難しく、みんなバラバラでは、結局、混乱してしまいます。

#コード02
taro_name = '太郎'
taro_kokugo = 50
taro_sansuu = 45
taro_heikin = (taro_kokugo + taro_sansuu) / 2

hanako_name = '花子'
hanako_kokugo = 90
hanako_sansuu = 85
hanako_heikin = (hanako_kokugo + hanako_sansuu) / 2

print(taro_name, 'の平均点:', taro_heikin)
print(hanako_name, 'の平均点:', hanako_heikin)

(3) def関数を用いる(第3段階)

コード03は平均値の計算で、def関数(2~6行目)を使いました。 def関数も大きなプログラムを組むときに威力を発揮します。 なお、3~5行目は関数の簡単な説明文です。関数heikin(kokugo, sansuu)の説明文を読むと、具体的な計算内容がわからなくても、 入力値としてkokugo, sansuuを入力すれば、戻り値として平均値を返してくれることがわかります。

この例は、誰もがすぐに分かるような計算ですが、もし、平均値の計算に非常に複雑で難解な計算が必要だとしたらどうでしょうか。 def関数を使わなければ、どこからどこまでが平均値を求める部分かを知ることすら難しいと思います。しかし、平均値の計算部分をdef関数を用いて他のコードと明確に分ければ、読みやすいコードになりますし、他のプログラムで使いたい場合も、def関数の部分をそのままコピーすればよいのです。入力値と出力値だけ知っていれば、計算内容を知らなくてもかまいません。

#コード03
def heikin(kokugo, sansuu):
    '''国語と算数の平均点をの算出
    (入力値) kokugo:国語の点数、sansuu:算数の点数
    (戻り値) 国語と算数の平均点'''
    return (kokugo + sansuu) / 2

taro_name = '太郎'
taro_kokugo = 50
taro_sansuu = 45
taro_heikin = heikin(taro_kokugo, taro_sansuu)

hanako_name = '花子'
hanako_kokugo = 90
hanako_sansuu = 85
hanako_heikin = heikin(hanako_kokugo, hanako_sansuu)

print(taro_name, 'の平均点:', taro_heikin)
print(hanako_name, 'の平均点:', hanako_heikin)

(4) 構造体の利用(第4段階)

実は、私はクラスを最初に勉強したときに「ああ、そうか。クラスって、 C言語などの 構造体が進化したものなんだなあ。」と理解しました。なお、PythonにはC言語のような「構造体」はありませんので、コード04では、Pythonの「クラス」の機能の一部を利用して「構造体」のように使っています。

構造体は、意味の近いデータの変数をまとめてグルーピング「カプセル化」するものです。関数は構造体にまとめることはできません。 8~13行目では 、name, kokugo, sansuu, heikin という4つの変数をカプセル化しています。16行目で定義された変数taroや、22行目の変数hanakoへは、いずれも、この4つの変数をカプセル化したものを代入しています。このように、4つの変数をカプセル化することで、これらの変数が互いに関係の深い変数同士であることを明示できます。プログラムが巨大化し、膨大な数の変数があるときに、それぞれの変数の関係を読み解くのは大変な作業です。大きなプログラムになるほど、構造体のように変数をまとめる機能を使うことで、わかりやすいコーディングを行うことができます。

余談ですが、実はPythonのクラスの変数は後から追加できるので、 文法的には10~13行目は必要なく、passに置き換えても同じ結果となります。しかし、読みやすいプログラムをコーディングするには、コード04のようにカプセル化する変数名を明示することの意義は大きいのではないかと思います。

#コード04
def heikin(kokugo, sansuu):
    #入力:国語の点数、算数の点数
    #戻り値:国語と算数の平均
    return (kokugo + sansuu) / 2

#構造体もどき。4つの変数をカプセル化
class tokuten_data:
    def __init__(self):
        name = ''
        kokugo = 0
        sansuu = 0
        heikin = 0.0

#taroは4つのデータをカプセル化した変数
taro = tokuten_data()
taro.name = '太郎'
taro.kokugo = 50
taro.sansuu = 45

#hanakoも4つのデータをカプセル化した変数
hanako = tokuten_data()
hanako.name = '花子'
hanako.kokugo = 90
hanako.sansuu = 85

taro.heikin = heikin(taro.kokugo, taro.sansuu)
hanako.heikin = heikin(hanako.kokugo, hanako.sansuu)
print(taro.name, 'の平均点:', taro.heikin)
print(hanako.name, 'の平均点:', hanako.heikin)

(5) いよいよ、クラスの仕様(第5段階)

いよいよ、クラスを用いたコードです。コード05では、3~11行目でクラスの定義を行っています。name, kokugo, sansuu, heikinという4つのデータだけではなく、heikin_cal()というメソッド(関数)もひとまとまりにし、カプセル化しています。(細かい文法は後回しにしましょう。この記事では説明しません。)

このようにクラスを記述することで、name, kokugo, sansuu, heikin, heikin_cal()が、お互いに関係のあるデータやメソッド(関数)であることがわかります。そして、このようにひとまとまりになったものを「オブジェクト」とよび、14行目では、taroという名前のオブジェクトを生成し、21行目ではhanakoという名前のオブジェクトを生成しています。

そして、taro, hanakoでは、それぞれ、16, 17行目、23, 24行目でkokugo, sansuuに国語と算数の点数を代入し、18, 25行目でheikin_cal()を実行すると、それぞれの計算結果をtaro.heikinとhanako.heikinnに保存することができます。def関数のように「データを操作する」だけではなく、taroとhanakoを操作した結果を個別に保存することができるのです。

#コード05
#データ4つとメソッド(関数)1つをカプセル化
class tokuten_data:
    def __init__(self):
        name = ''
        kokugo = 0
        sansuu = 0 
        heikin = 0.0
        
    def heikin_cal(self):
        self.heikin = (self.kokugo + self.sansuu)/2

#変数taroに、データ4つとメソッド(関数)1つをカプセル化したオブジェクトを代入
taro = tokuten_data()
taro.name = '太郎'   #nameに'太郎'を代入
taro.kokugo = 50     #kokugoに50を代入
taro.sansuu = 45     #sansuuに45を代入
taro.heikin_cal()  #メソッドheikin_cal()の実行

#変数hanakoに、データ4つとメソッド(関数)1つをカプセル化したオブジェクトを代入
hanako = tokuten_data()
hanako.name = '花子'   #nameに'花子'を代入
hanako.kokugo = 90     #kokugoに90を代入
hanako.sansuu = 85     #sansuuに85を代入
hanako.heikin_cal()  #メソッドheikin_cal()の実行

print(taro.name, 'の平均点:', taro.heikin)
print(hanako.name, 'の平均点:', hanako.heikin)

そして、これをクラスの説明でよく表現される方法で例えると、 3~11行目のクラスの定義ではtokuten_dataという名前の「設計図」を定義し、この設計図をもとに14行目ではtaroという名前の「製品」をつくり、21行目ではhanakoという名前の「製品」を作っています。

ここで、taroやhanakoは、「データ」や 「データを操作する方法(メソッド)」を、ひとまとまりにした「オブジェクト」のひとつですが、 「オブジェクト」の中でも、特にクラスを用いて設計図から作られたtaroやhanakoのような「製品」のことを、「インスタンスオブジェクト」あるいは単に「インスタンス」と呼びます。

いかがでしょうか。コード01と比較して、コード05は長いコードになってしまいました。簡単なプログラムでは、むしろクラスを使わない方がわかりやすいと思います。しかし、プログラムが複雑になればなるほど、クラスを用いた方が、読みやすいコードになります。

なお、def関数と違うのは、以下の2点です。
①クラスは「データ」だけでなく「データを操作する手続き(メソッド)」もカプセル化して「オブジェクト」を生成することができます。
② カプセル化して生成したオブジェクトに個別の名前をつけることができ、それぞれのオブジェクトは個別の「データ」を記憶したり、読み出したり、変更したりでき、更に「データを操作する手続き(メソッド)」を 実行できます。

3. クラスの「カプセル化」以外の機能

私はクラスを最初に見た時に「結局、クラスってなんなの?」と頭の中が「???」になりました。この記事でクラスの存在意義を少しでも感じていただければ、うれしいです。

また、この記事では、クラスの「カプセル化」による恩恵を説明してきました。しかし、「カプセル化」以外にも、既にあるクラスに機能を追加したり、一部の機能を改変したりできる便利な仕組みがあります。Pythonでは「継承」を理解することにより、その仕組みを利用することができます。新しいプログラムを作るときに、一から作る必要はないのです。 すでにある有益なプログラムを活用する。それがオブジェクト指向プログラミングが目指す方向なのです。

なお、オブジェクト指向プログラミングでは、「カプセル化」「継承」「ポリモーフィズム(多態性、多様性)」 という言葉が重要キーワードです。「ポリモーフィズム」はPythonでは「オーバーライド」という方法を用いて実現しています。 クラスのメソッドのオーバーライドにより、元になるクラスのメソッドを定義し直すことが可能であり、柔軟性のある継承が可能となります。 これらのキーワードは名称だけ、頭の片隅に残しておいてください。

私が実際に購入した教材のご紹介

以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。

Python♪私が購入したPythonの書籍のレビュー

UdemyのPythonの動画講座を書籍を買う感覚で購入してみた