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

クラスとオブジェクトは設計図と製品に例えられますが、なぜ、そんな例えになるのでしょうか。そして、オブジェクトを生成する必要があるのでしょうか。クラスの基本文法は学習したけれど、モヤっとした感じが消えない人が多いのではないでしょうか。

この記事は、私が最初にクラスを勉強したときのモヤモヤについてまとめたものです。

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

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

1.はじめに

わかるんだけど、しっくりこない。これが最初にクラスを勉強したときの正直な感想ではないでしょうか。

何が分からないのかと聞かれても、何が分からないのかもわからない。基本的な文法は勉強して、def関数に比べてクラスは機能が充実していて、オブジェクト指向という難解な考え方を実現するために必要なものであることは漠然とわかる。

(でも・・・)

なのです。この「でも・・・」が消えない。

こんなモヤモヤがある方は、もしかしたら以下の疑問を曖昧にしたままだからなのかもしれません。

「def関数のときにはオブジェクトを作らなくてもすぐに使えたのに、なぜクラスではクラスを定義したあとにオブジェクトを作る必要があるのか」

使いこなしている人には当たり前すぎることなのかもしれませんが、私はこれを意識するようになったらすっきりしました。

上記の疑問に対して「そうそう、それそれ!」と思った方は、この記事が役に立つかもしれません。

なお、この記事ではクラスの文法の説明はありません。そこで、以下の基本文法だけはサラッと予習しておいて下さい。

  1. コンストラクタ ※「 __init__(): 」の部分のことです
  2. クラス変数とインスタンス変数の違い
  3. クラスのメソッドの定義
  4. クラスからオブジェクト(インスタンス)の生成

なお、「継承」「オーバーライド」といったクラスの応用的な知識は必要ありません。

クラスの勉強では、上記1~4の基本項目だけ勉強したら「継承」とか「オーバーライド」は後回しにし、自分がdef関数を使って作ったコードをクラスに書き換えてみてください。どんどん使って慣れた方がよいと思います。クラスの勉強は「習うより慣れろ」という言葉がピッタリあてはまります。

def関数で書けるものはクラスでも書ける。クラスで書けるものはdef関数でも書ける。使ってみれば、それぞれの良いところが後からわかるものだと思います。

2.クラスの意義を知るのは実は簡単

リストの意義を知るのは実は簡単なんです。なぜなら、よくお世話になっているlistもクラスの考え方から生まれたものだからです。

さて、コード01はlistの使用例ですが、内容の異なるリストをaやbという別々の変数に保存することができます。また、sort(), append()といったメソッドが最初から用意されていて、listの内容を操作することも可能です。

pythonにおいてlistが非常に便利なツールであることは周知の事実ではないでしょうか。listが便利だと感じている方は、同じ理由でクラスも便利だと思うはずです。

クラスを使ったプログラミングとは、listのような便利なツールを自作するための仕組みなのです。

#コード01
a = [1, 2, 7, 3]
print(a)
a.sort()
print(a)
a.append(4)
print(a)

b = [2, 4, 5, 6]
print(b)

3.listを少し違った側面からみてみよう

リストは標準で用意されたデータ型であり、あらかじめ仕様が決められています。言い換えれば、listの設計図が最初からできあがっているのです。

しかし、ここで注意しなければならないのは、listは設計図だけではなにもできません。a = [1, 2, 3, 4]のように具体的にリストを宣言しないとなにもできないのです。

このように、具体的に生成されたものは「オブジェクト」と呼ばれています。

つまり、立派なlistの設計図があっても、listのオブジェクトを作らなければ使えないということです。

listのオブジェクトには値が代入できるだけではなく、設計図で用意されていたsort()、append()といった便利な関数(メソッド)が用意されており、様々な操作を行うことができます。

例えば、コード01の変数aと変数bは同じlistという設計図からできあがったものですが、全く別のオブジェクトであり、オブジェクトにはそれぞれ独立した値を保持できます

ここまでの説明で、特に赤字の部分を意識して、じっくり読み返していただきたいと思います。

4.def関数のオブジェクト

さて、次にdef関数のオブジェクトについて説明します。

「オブジェクト」という言葉が気になるかもしれませんが、あまり気にせずに読んでください。「オブジェクト」という言葉は定義が複数存在し、言葉の定義がモヤッとしています。この記事では「具体的な命令文を実行したりデータを記憶するためにパソコンのメモリー内に保存されたものであり、パソコンが理解できる形式に変換されたデータのまとまり」ぐらいの感覚で読んでください。正確な定義ではありませんが、「当たらずとも遠からず」です。

さて、def関数は関数を定義しただけでいきなり使えます。しかし、入力値に対して計算結果を出力することしかできず、関数にデータを記憶させることはできません。

コード02では、国語の点数と算数の点数を関数heikinn()に引数で渡すと、関数heikin()は平均点をreturnしてくれます。

#コード02
def heikin(kokugo, sansuu):
    return (kokugo + sansuu) / 2

print(heikin(20, 30))
print(heikin(90, 80))

この命令文の実行を図にすると以下のようになります。黄色の箱の部分がコード02の2, 3行目のPythonのコードを元にPCのメモリー内に生成されたオブジェクトです。

このオブジェクトに引数で20, 30を渡すと、平均点85が返ってきます。90, 80を渡すと85が返ってきます。

def関数のオブジェクト

ここで重要なのはdef関数のオブジェクトはPC内に1つしかないことです。このオブジェクトにデータを渡すと、何度でも同じ計算をしてくれます。

def関数は、このように1つのオブジェクトで何度も同じ処理をしてくれるところが長所でもあり、短所でもあります。

短所でもあると言ったのは、複数のデータを記憶するしくみを作ることが難しいことです。

したがって、def関数にはデータを記憶する機能はなく、例えば太郎君や花子さんの点数をdef関数に記憶するといったことはできません。したがって、計算結果を記憶したい場合はコード03のように別の変数を用意する必要があります。

#コード03
def heikin(kokugo, sansuu):
    return (kokugo + sansuu) / 2
taro_heikin = heikin(20, 30)
hanako_heikin = heikin(90, 80)
print(taro_heikin)
print(hanako_heikin)

もちろん、def関数のオブジェクトの中に、複数のデータも記憶するというアイデアも考えられますが、複雑で巨大なオブジェクトになってしまいますので、Pythonではそのような仕様になっていません。

5.def関数の短所の解決イメージ

では、listのように複数のデータを保存するためにはどのような仕組みが必要なのでしょうか。それは、オブジェクトを複数つくればよいのです。

下図はイメージ図ですが、PCのメモリー上に複数のオブジェクトを作れば、それぞれに別々のデータや計算結果を記憶させることができます。

複数のオブジェクトを生成

例えば、平均値を計算するdef heikin()オブジェクトのクローンを複数生成し、それぞれに太郎君や花子さんのデータを一緒に記憶させればよいのです。どうでしょうか。listのイメージに近づいてきたと思いませんか。

複数のオブジェクトを生成するからこそ、それぞれに別の値を保存したり、次郎君のデータを追加したりすることも可能です。面倒なようでもオブジェクトを別々に作る意義がここにあります。

このアイデアのイメージを実現したのがクラスの考え方なのです。

6.クラスのオブジェクト

コード05は、クラスを使用したコードのサンプルです。4つのデータと1つのメソッドheikin_cal()を定義しています。

#コード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)

それでは、このコード05を図で順を追って説明します。

まず、コード05の3~11行目では設計図であるクラスTokuten_dataを生成します。

ここで重要なのは、クラスはただの設計図なので、クラスに変数を代入したりクラスの中の関数(メソッド)を実行することはできません。ただの計画なのでクラスだけではなにもできません。まだ、未完成品なのです。

クローンのオブジェクトをたくさん作るための前準備がクラスなのです。

クラス生成の図

設計図ができたら、次は14行目のtaro = tokuten_data()、 21行目のhanako = tokuten_data()でオブジェクトを生成します。

設計図(クラス)から、オブジェクト(インスタンス)を生成することで初めて代入や実行が可能になります。

オブジェクトを生成しなければならないので、手間が増えますが、生成したオブジェクトの数だけ複数のデータを記憶することができます。

下図ではオブジェクトtaroとhanakoは、それぞれ4つの変数を記憶でき、heikin_cal()を実行できます。そして、次郎君が転入してくれば、オブジェクトjiroを追加すればよいのです。

このようにクラスからオブジェクトのクローンを複数つくることができるので、クラスが「設計図」、オブジェクトが「製品」に例えられることが多いのです。

クラスのオブジェクトのイメージ図

さっそく、内容を書き換えてみましょう。15~18行目、22~25行目ではオブジェクトの内容が変更されています。taroやhanakoはそれぞれ独立したオブジェクトなので、下図のように別々のデータを保存することができます。

クラスのオブジェクトの内容変更

どうですか。設計図をつくり、その設計図をもとにオブジェクトをどんどんつくる。合理的な方法だと思いませんか?

「taro = Tokuten_data()」のような、オブジェクトを生成する手順は意味がなさそうに思うかもしれませんが、個別のデータを記憶するためには重要なステップなのです。

また、def関数と同じ感覚だと、クラスであるTokuten_dataは、そのまま使えそうな気がしますが、基本的にクラスだけでは使えません。

なお、クラスから作ったオブジェクトはインスタンスとも呼ばれますので、ついでに覚えておきましょう。

7.もう一度listと比較してみよう

さて、もう一度、自作のクラスであるコード05とlistを比較してみましょう。

クラスに慣れていないと、def関数のときのようにクラスを作っただけですぐに使えるような感覚になりますが、その感覚を断ち切りましょう。クラスの場合は単に設計図を用意しただけなので、これだけでは基本的になにもできません。

これはlistの場合も同じなのです。listは標準で最初からクラスが定義されているというだけであり、a = [1,2,3,4]のようにオブジェクトを生成する必要があります。

そして、listのa = [1,2,3,4]と同様に、クラスでもtaro = Heikin_class()を実行することでオブジェクトtaroを生成しているのです。

8.それなら、def関数は不要なのか

さて、これほど便利なクラスであるならば、def関数は不要なのでしょうか。いいえ、そうではありません。

def関数はオブジェクトが1つしかないことが長所でもあるのです。「繰り返し同じ計算をするが、関数への入出力データを記憶する必要がない」場合、クラスのように複数のオブジェクトを生成すると、オブジェクトを生成するたびに消費メモリーも計算時間も増加します。

(ただ、クラスもオブジェクトを1つしか作らず、同じオブジェクトを使い回せば消費メモリも増えませんのでdef関数と同じようなことはできます。)

クラスがよいのかdef関数がよいのかという問題は、小規模なプログラムでは問題になりませんが、計算速度や消費メモリが課題となるプログラムでは、可読性も考慮しながら頭を悩ませる問題です。

9.まとめ

クラスを勉強するとき、オブジェクト(インスタンス)を生成する意義を理解し、listやdef関数との違いを理解する必要があります。

def関数で記述されたコードはクラスに書き換えることができます。以下の基本文法を勉強したら、「継承」「オーバーライド」のような応用は後回しにして、クラスをどんどん使ってみましょう。「習うより慣れろ」です。

  1. コンストラクタ ※「 __init__(): 」の部分のことです
  2. クラス変数とインスタンス変数の違い
  3. クラスのメソッドの定義
  4. クラスからオブジェクト(インスタンス)の生成

なお、わかりやすさを優先して、「厳密に言えば・・・」という部分が多々あることと思いますがお許しください。

10.関連記事

クラスに慣れないうちは、def関数をどのようにクラスに書き換えればよいのか分かりにくいと思います。そのような場合は、以下の記事を参考にしてください。

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

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

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

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

その他

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

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