初心者がPythonのクラスについて理解しようとする話
はじめに
これまで、私が公開したいくつかのコードでは、写経部分ではたまに使っているかもしれませんが、自分のコーディング部分にはこれでもかというくらいにclassを使っていません.
というのは、全然使い方が理解できていないからです.それに関数defがあれば十分なのではと考えていました.
しかし、例えばKerasのドキュメントなんかにいくと
ImageDataGeneratorクラス
とか当然のようにclassが使われています.チュートリアルやサンプルが充実しているので、見よう見まねで使えてしまうのですが、少しは理解しておかないと痛い目見るだろうと思い、ようやく調べてみることにしました.
注記)記事中に、classとクラスが混在してしていますが、基本的に同じ意味です.ややこしくて申し訳ありません.
classに対するイメージ
現時点で、私がclassに対して持っているイメージです.
- 複雑そう.
- 使えたら便利そうだけど、defでがんばれば、使えなくても大丈夫そう
- よくselfってみるけどなんや・・.
こんなレベルですので、コーディングの前に少し言葉の意味などを調べるところから始めてみます.
class(クラス)とは
まず、classっていったい何なのか、その定義を調べようとしたのですが、 - classがあれば同じコードを何度も書く必要がなくなります.
とか、 - classを理解するには、オブジェクト指向をまず理解・・.
のようになります.定義を調べたいのですが、コード中のclassの定義の仕方や、使い方の説明ばかり出てきます.
こんな質問まで見つかりました(笑)
Python - クラスって存在する意味あるんですか?|teratail
おそらく、classそのものの意味を定義することは難しくて、なるほどこういったことをできる機能をclassというのだと後で理解できるように なるのだと期待しておきます.
ですので一旦、定義のことは忘れてclassで何ができるかを調べていきたいと思います.
なお、私は当然のようにオブジェクト指向を理解していませんでしたので、それについても少し調べました.
こちらもclassに負けず劣らず理解の難しい得たいの知れないやつなんですが、オブジェクト指向に関しては全く分からない方にとっては以下の記事が参考になるかと思います.あくまで概念の話ではあるのですが、一番分かりやすかったです.こういう記事を書けるようになりたいです.
classでできること
classについてのこちらの記事では、のように書いています.
classを使用すると、関数を定義するときのように複数の処理や値をまとめておくことができる.
classからは、『インスタンス』というものが作成でき、インスタンスにはデータを保持させておくことができたり(属性)、関数のように処理をまとめておくことができたり(メソッド)します.このように『1つのもの(オブジェクト)に情報や処理をひも付けて管理できる』のがclassです.
見慣れない言葉がたくさん出てきて心が折れそうになりますが、私なりの理解も加えながら1つずつ見ていきます.
- インスタンス:クラス(設計図)をもとに具体化したもの.量産される何か.
- 属性:インスタンスごとに持つ個別のデータ.
- メソッド:クラスの中で呼ぶ関数のこと.のちほど出てくるコンストラクタinit() もメソッドの一つ.defで定義する関数はクラスの中ではメソッドと呼ばれているんだな、と捉えて多分大丈夫です.
- オブジェクト: 数値、文字列、関数などすべてを指す.これもあまり深く考えなくてよさそう.というか、ガチ勢でなければたぶん深く考えてはいけない.オブジェクト指向と呼ばれるプログラム言語で扱うモノは全てオブジェクトくらいの勢いでOK.
なんとなく掴めてきたイメージは、クラスっていうベースになる設計図があって、それがあればベースの機能(関数とか)が使えるインスタンスと呼ばれるオブジェクトが量産できるってこと.でも、まだdefで定義する関数との違いが良く分かりません.
関数を集めたファイル:モジュール
いわゆる関数だけを集めたまとまりとしてモジュールがあります.モジュールというのは、tools.pyとかのように名前が付けられたPythonファイルのことで、通常そのファイルの中にはたくさんの関数が書かれます.(ファイルから読み込む機能のことをモジュール機能と言うっていう解釈の方が正しいかもしれません)
例えば、tools.pyファイルの中に、runrun, hithitという名前の関数を定義している場合、以下のようにすれば、関数runrunおよび関数hithitを使用することができるようになります.
from tools import runrun
from tools import hithit
複数行書くのが面倒な場合、from tools import *
でも取り込めますが、この場合ファイル内の全ての関数を取り込んでしまいますので、余りスマートではありません.
ライブラリやパッケージとは
モジュールと同じように、複数の関数を使ったりするライブラリやパッケージというものがあります.これらもモジュールと同じように使えます.
詳しく理解できていないので、あくまでイメージとして捉えてほしいですが、乱暴に書けば、モジュールが複数の関数が書かれたファイルだとすると、ライブラリは複数のモジュールが集まったもの、パッケージは複数のライブラリおよびモジュールが集まったものです.
さらに乱暴に言ってしまえば
パッケージ >ライブラリ > モジュール.でも使い方は同じ
というイメージで問題ないかと思います.
クラスの呼び出し方
話をクラスに戻しますが、クラスもモジュールと同じような書き方で使うことができます.クラス
例えば、my_school.pyというファイルの中に、Studentというクラスを持っているとします.同じファイルの中に他のクラスが入っていても問題ないです.また、クラスを別ファイルで作成しなければいけないわけでもありません.
from my_school import Student
呼び出し方に違いがないことが分かってどうするって思いますよね.私もです.
具体的にコーディングしてみる
結局、ここまでしてきた話では(少なくとも私は)、やはりまだクラスの恩恵を感じていません. こうなったら恩恵が得られることを信じて、具体的にクラスを使ったコードを書いていきたいと思います.
以下のような設定となるclassを作っていきます.
例1 class Student
classの定義:
classは、関数とほぼ関数と同じように書きます.ここでは、Studentというクラスを作成します. クラス名の最初の1文字目は大文字とするのが慣例のよう.2単語以上つなげる場合は、単語ごとに最初の文字を大文字にしましょう.
class Student: pass # ただ定義するだけなので、このクラスは何の機能もありません.
これで、学生インスタンスを作り出す設計図(量産工場)ができました.
インスタンスの作成:
では、各学生に相当する具体的なインスタンス(オブジェクト)を作ってみます. 以下のように、作成したclass(ここではStudent)を変数に代入すれば作成できます.
student1 = Student()
ここのstudent1
というのが、各プレイヤーごとに与えるインスタンス名になります。これでstudent1という中身が空っぽの学生インスタンスが現れたことになります.
属性を与える:
学生にの名前・身長・体重という情報を、属性として与えます.student1に与える場合、次のように書けます.
student1.name = "matsuzaka" student1.height = 183 student1.weight = 93
同様に以下のようにすればstudent2を作成できます.
student2 = Student() player2.name = "mori" player2.height = 170 player2.weight = 80
まだ、恩恵は何も感じていないと思いますが、もう少しお付き合いください.
class内の関数であるメソッドを使う
上の例では、nameやheightを一行ずつ与えていますが、少し面倒ですしスマートではありません.class内の関数であるメソッドを使えば、もう少し省略することができます.
メソッドは関数と同様にdef():
という形式で書くことができますが、第1引数にselfというオブジェクトを指定する必要があります.
selfもなかなか理解が複雑な曲者なんですが、self:自身という名前が示すとおり、メソッドを呼び出したインスタンス自身のことを示します.具体的なイメージを持つため、実際にselfを使って、classにメソッドを追加してみます.
# メソッドを使用したクラス class Student(): def set_data(self, name, height, weight) self.name = name self.height = height self.weight = weight # 自己紹介するメソッド def say_my_data(self): print(print("I am {}. My height is {}cm. My weight is {}kg." .format(self.name, self.height, self.weight))) # BMIと適正体重を言うメソッド def my_BMI(self): bmi = round(self.weight /(self.height/100.)**2) best_weight = round((self.height/100.)**2 * 22) print("My BMI is {}. My BEST weight is {} kg." .format(bmi, best_weight))
メソッドを実行する(引数を与える)側ではselfは引数には指定してはいけません.メソッド側の第2引数から指定してください.
- メソッドを使えるように書き換えた後
student1 = Student() # インスタンスを作成 student1.set_data("matsuzaka", 183, 93) # 属性を一気に設定
- 参考:メソッドを使う前はこれ(再掲):
student1 = Player() student1.name = "matsuzaka" student1.height = 183 student1.weight = 93
以下を実行すると、
player1.say_my_data() player1.my_BMI()
以下のように出力されます
I am matsuzaka. My height is 183cm. My weight is 93 kg.
My BMI is 19. My BEST weight is 79 kg.
インスタンスを作成する際に呼び出すメソッド:コンストラクタ
これで、学生の量産が比較的簡単にできるようになりました.ですが、量産部分のコードを見て思われるかもしれませんが、インスタンスを設定→属性を設定という作業が地味に面倒です.
そこで、コンストラクタというものが登場します.コンストラクタはインスタンス作成と同時に自動的に呼び出されるメソッドのことを指します.
コンストラクタを使うことで、先の例でset_dataメソッドを呼んでいたstudent1.set_data(引数)
の一行を省略することが可能になります.
コンストラクタ__ini__()
を使う
コンストラクタは、メソッド名が決まっており__init__()
と定義する必要があります.
# コンストラクタを使ったPlayerクラスの定義 class Student: def __init__(self, name, height, weight): self.name = name self.height = height self.weight = weight
これで、大分スマートにインスタンス(学生オブジェクト)を量産することができます.今は属性の情報が非常に単純なんでこれが一体何になるのかいまいちピンとこないかもしれません.
例えば、対戦ゲームなんかで、取れる行動の種類などは同じだけど、置かれている状況は違う.といった場合なんかに、それぞれのインタンスの管理がしやすいんじゃないかと思います.たぶんマルチエージェントになるとクラスを使わないと無理なんじゃないかと思えてきました.
すみません.語彙力がなさすぎてうまく説明できない・・・.
クラスの継承
気を撮り直して説明を続けます.classの特徴の1つがここで説明する継承機能と呼ばれるものです.
この機能を利用することで、既に作成したclassをベースに、機能の追加を容易に行うことができます.
先ほど作成したクラス(Student)をベースにして、Universityクラスを作ってみます.
classの継承は以下のように、新しく定義するclass名に続く( )内に、継承したいclass名を指定すればOKです.
class University(Student): pass
もし、上のようにその他のコードを何も触らなかった場合、UnivesityクラスとStudentoクラスは全く同じになります.
- Universityクラスからインスタンスを作成して
student1 = University("matsuzaka", 183, 93)
- メソッドを実行
student1.say_my_data() student1.my_BMI()
- これまでと全く同じ出力をするだけです.
I'm matsuzaka. My height is 183cm. My weight is 93 kg.
My BMI is 28. My BEST weight is 74 kg.
Universityクラスのコンストラクタを拡張し、大学名と出身県を紹介するセリフを追加してみます.
class University(Student): def __init__(self, name, height, weight, univ_name, pref): super().__init__(name, height, weight) self.univ_name = univ_name self.pref = pref def say_my_univ(self): print("I study at {}, I'm from {} prefucture" .format(self.univ_name, self.pref))
インスタントstudent3を作成します.コンストラクタの引数を増やしたので、大学名と出身県を追加しています.
student3 = University("uchikawa", 185, 93, "Fukuoka Univerisity", "Ohita")
say_my_dataメソッド、my_BMIメソッドは継承されているので、これまでと同じように呼び出すことができます.
student3.say_my_data() student3.my_BMI() student3.say_my_univ()
この場合、出力は以下のようになります.
I'm uchikawa. My height is 185cm. My weight is 93 kg.
My BMI is 27. My BEST weight is 75 kg.
I study at Fukuoka Univerisity, I'm from Ohita prefucture
継承の継承もできる
なお、以下のように継承したクラスの継承も可能です.
class Doctor(University): def __init__(self, name, height, weight, univ_name, pref, study): super().__init__(name, height, weight, univ_name, pref) self.study = study def my_study(self): print("I study about {} at {} as posdoc" .format(self.study, self.univ_name))
以下を実行すると
student4 = Doctor("yamada", 180, 76, "Osaka Univerisity", "Hyogo", "Yakult") student4.say_my_data() student4.my_BMI() student4.say_my_univ() student4.my_study()
こう出力されます.
I'm yamada. My height is 180cm. My weight is 76 kg.
My BMI is 23. My BEST weight is 71 kg.
I study at Osaka Univerisity, I'm from Hyogo prefecture.
I study about Yakult at Osaka Univerisity as posdoc
例2 class Countman
ただ、指定した範囲の偶数と奇数をリスト化するだけのクラスを作ってみます.
class Countman(): def __init__(self,start, finish): self.start = start self.finish = finish def count_even(self): list = [] for i in range(self.start, self.finish+1): if i % 2 == 0: list.append(i) return list def count_odd(self): list = [] for i in range(self.start, self.finish+1): if i % 2 == 1: list.append(i) return list
インスタンスを作成
指定範囲の異なる2つのインスタンスを作成
countman1 = Countman(20, 30) countman2 = Countman(1220, 1230)
結果の確認
それぞれのインタンスに対して、メソッドを実施
print(countman1.count_even()) print(countman1.count_odd()) print(countman2.count_even()) print(countman2.count_odd())
出力は以下.大丈夫そうです.
[20, 22, 24, 26, 28, 30]
[21, 23, 25, 27, 29]
[1220, 1222, 1224, 1226, 1228, 1230]
[1221, 1223, 1225, 1227, 1229]
まとめ
クラスについて、少し勉強しました.
今回書いたコード程度では、その恩恵を感じられたわけではないのですが、なんか便利そうだということは少し理解できました.
例えば、マルチエージェントのように同じような操作を並列に行う場合などの管理には必須と言えそうです.
使い方を体に覚えさせるため、積極的に使っていきたいと思います.
以下の記事を参考にしました.ありがとうございました.
具体例で学ぶ、Pythonのclassの使い方 | 株式会社キャパ CAPA,Inc.
【入門者向け】Pythonのクラスを習得する方法を詳しく解説! | CodeCampus
Pythonでclass(クラス)を使う方法【初心者向け】 | TechAcademyマガジン
【Python入門】クラスの使い方やオブジェクト指向の概念を理解しよう | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト