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言語などの 構造体が進化したものなんだなあ。」と理解しました。C言語の構造体は、意味の近いデータの変数をまとめてグルーピング「カプセル化」するものです。でも、構造体は関数をまとめることはできません。

さて、PythonにはC言語のような「構造体」はありませんので、コード04では、Pythonの「クラス」の機能の一部を利用して「構造体」のように使ってみます。

8~13行目では 、name, kokugo, sansuu, heikin という4つの変数をカプセル化しています。クラスTokuten_dataが4つの変数で構成されていることを定義しているのです。Pythonでは値を代入しないと変数が定義できないので、0などの適当な値を代入しています。

16行目や22行目は、いずれも、taroやhanakoが4つの変数をカプセル化した構造体であることを定義しています。そして、「taro.kokugo」「taro.sansuu」のように、「構造体名.カプセル化した変数名」という書式で各変数にアクセスすることができます。

このように、4つの変数をカプセル化することで、これらの変数が互いに関係の深い変数同士であることを明示できます。プログラムが巨大化し、膨大な数の変数があるときに、それぞれの変数の関係を読み解くのは大変な作業です。大きなプログラムになるほど、構造体のように変数をまとめる機能を使うことで、わかりやすいコーディングを行うことができます。

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

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

#構造体もどき。4つの変数をカプセル化
class Tokuten_data:
    def __init__(self):
        self.name = ''
        self.kokugo = 0
        self.sansuu = 0
        self.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()というメソッド(関数)もひとまとまりにし、カプセル化しています。heikin_cal()というメソッドは、kokugoとsansuuの平均を計算し、heikinという変数に代入する関数です。(細かい文法は後回しにしましょう。この記事では説明しません。)

このようにクラスを記述することで、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):
        self.name = ''
        self.kokugo = 0
        self.sansuu = 0 
        self.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は、「データ」や 「データを操作する方法(メソッド)」を、ひとまとまりにした個別の製品なのです。

さて、実はPythonでは様々なものが「オブジェクト」と呼ばれており、taroやhanakoも「オブジェクト」のひとつです。しかし、特にクラスという設計図から作られたtaroやhanakoのような「製品」のことを、「インスタンスオブジェクト」あるいは単に「インスタンス」と呼びます。

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

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

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

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

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

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

4.関連記事

この記事は、def関数をクラスに書き換えることによって、クラスのカプセル化の意義と記述方法を説明したものですが、別の切り口でクラスを説明した記事を用意しました。

Python♪基本:クラスの初学でのモヤモヤを解消しよう

もし、「def関数のときにはオブジェクトを作らなくてもすぐに使えたのに、なぜクラスではクラスを定義したあとにオブジェクトを作る必要があるのか」という問いが難しいと感じる場合には、こちらの記事の方がモヤモヤを解消できるかもしれません。

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

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

Python♪私が購入したPythonの書籍のレビュー
UdemyのPythonの動画講座を書籍を買う感覚で購入してみた

その他

Twitterへのリンクです。SNSもはじめました♪

液晶ペンタブレットを買いました
 (1) モバイルディスプレイを買うつもりだったのに激安ペンタブレット購入

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?