この記事には、PR部分にUdemyのアフィリエイトリンクを含みますが、記事の内容は筆者が独自に作成したものです。記事の作成についてUdemy様から報酬等を受け取って作成したものではありません。
オブジェクト指向とは
Pythonを学んでいると、よく「Pythonはオブジェクト指向である」というフレーズを目にするでしょう。クラスを作成する前に、その意義を考えてみましょう。
オブジェクト指向とは何かを簡単に説明すると、データの記述方法(規格・部品の仕様)を定め、その後に、その規格に沿って作られたデータ(部品)に適用できる「メソッド」を定めるプログラミング手法のことです。
データの記述方法をいちいち定めることは、一見面倒に思えますが、実は大きなメリットがあります。記述方法が統一されていることで、メソッドを適用したり、プログラムの別の部分でコードを再利用したりする際に、エラーが発生しにくくなるのです。
つまり、オブジェクト指向は、コードの再利用性と可読性を向上させる手法と言えます。
これらのメリットは、大規模なプロジェクト開発やプログラミングにおいて、より明確になります。
コードを書くたびにいちいち規格を定めるのは面倒くさいですが、それが結果的に、エラーの発生を減らし、コードの再利用による複雑なプログラミングを可能にしてくれます。
クラスを作成することは、規格と、規格に沿ったデータを作ることにほかなりません。
オブジェクト指向プログラミングの具体的な現れだと言っていいでしょう。
PR
クラス(class)の復習
さて、そもそもクラスとは何だったでしょうか。基礎編第6回で、Pythonにあらかじめ備えられているクラスについて学習しました。
これらのクラスは、それぞれデータの記述方法が定まっていました。一番わかりやすいのは、シーケンス型のリストとタプル、マッピング型の辞書でしょう。
list = [1,2,3,4]
tuple = (1,2,3,4)
dict = {'apple': 100, 'banana': 200, 'cherry': 300}
リストは、[ ]で囲まなければなりませんし、タプルは( )で囲まなければなりません。辞書に至っては、{ }で囲んだ中に、keyと値を : でつないだペアを記述する必要がありました。
このように、特定のクラスでは、特定のデータの記述方法が定まっています。
記述方法を定めることで、プログラマーは、特定のクラスに通用する便利な「メソッド」をエラー無く実行できる恩恵が受けられます。
自分でクラスを作成するということは、エラー無く効率的な「メソッド」を適用できるようにデータの記述様式を定義するということです。
クラス(class)の作成
では、前提知識の確認が終わったところで、さっそく何らかのクラスを作成してみましょう。
RPGのゲームを作成するとして、キャラクタークラスを作成してみましょう。
空っぽのクラス
クラスを作成する、もっとも原始的なコードは以下のようになります。
class Character:
pass
キャラクター(Character)クラスを作成するだけならば、上のコードで作成が終わります。ただし、内容的には何も定まっていません。
空っぽのクラスといったところです。
TABの後にpassを書いたのは、クラスを定義する「class Character:」の:(コロン)の後には何か指示を書かなければならないためです。
passというコマンドを、「このクラスでは何もしない」ことを指示するために書く必要があります。
クラスを呼び出す方法
空っぽのクラスを作成しただけでは、Characterというクラスがただ存在するだけでなんの機能も果たしません。
ただ、そのような空っぽのクラスでも、通常のデータ型と同じようにインスタンスを作成することが出来ます。
chara = Character()
今回、変数の名前をcharaとしましたが、内容がわかればどんな名前を付けても構いません。
空っぽのクラスであり、内容が何も定まっていませんので、Characterの引数もカラのまま「( )」を書きます。
クラスに中身を詰めこむ
実質的に何の意味もないコードではありますが、ここまでで、クラスを作成し、呼び出すことが出来るようになりました。
ここからは、生み出した空っぽのクラスに肉付けをしていく工程になります。
属性(アトリビュート)を定める
クラスの中身を詰めていきましょう。最初に定めるのは、Characterクラスが、どのようなデータどのような形で持っているかということです。
これを、属性(アトリビュート、attribute)と言います。
これからは、上記のclass2.pyを書き換えていく作業になります。
class Character:
name = ''
health = 0
strength = 0
ここで定めたのは、キャラクタークラスは、文字列の名前を持っており、healthというHPを整数で持ち、strengthという攻撃力を整数で持つということです。
上記のように書くことで、名前・HP・STRという3つの属性をCharacterクラスのデータが持つことになります。
※ここで、あれ?属性の書き方が間違ってるよ?と思った方で、これから何を説明するか理解している方は、__init__を使った属性定義の項に進んでください。
キャラクターの作成
ただ、これだけでは具体的なキャラクターについての情報がありません。一つ具体化されたサンプルキャラクターを作ってみましょう。
class Character:
name = ''
health = 0
strength = 0
frieren = Character()
frieren.name = 'Frieren'
frieren.health = 100
frieren.strength = 20
具体的な「frieren」、というキャラクターを作成し、クラスで定めた内容に沿って、名前とHPと攻撃力を定めました。
キャラクターの呼び出し
では、いったんこれらのコードを、character.pyとして保存し、別ファイルから呼び出してみましょう。呼び出すための新しいファイル名は、test_chara.pyとしておきます。
#%%
import character
print(character.frieren.name)
print(character.frieren.health)
print(character.frieren.strength)
dict_frieren=vars(character.frieren)
print(dict_frieren)
クラスファイルの読み込みには、モジュールの読み込みと一緒で、import文を使います。
呼び出しは、「クラスファイル名.インスタンス名.インスタンス属性」を指定します。「character.frieren.name」という感じです。
呼び出す際にprint()を使っているのは、使わなければ、出力が上書きされて、strengthの値しか表示されなくなってしまうためです。
一つの値を表示するだけならば、print()がなくても表示されます。
また、途中にある「dict_frieren = vars(character.frieren)」 という行が気になるかもしれません。
vars()関数は、frierenが持っている属性と属性値を辞書型で作り直す関数だと考えてください。
frierenというインスタンスの持っている属性を全て見るためには、{属性:属性値,…}という辞書型の表記が必要になるため、vars()関数を使用しています。
実行結果は以下になります。
Frieren
100
20
{'name': 'Frieren', 'health': 100, 'strength': 20}
これで、とりあえずはクラスを定義し、1つのキャラクターが作成できたということになります。
クラス変数
さて、何とかクラスとインスタンスの作成までこぎつけましたが、いま実行したクラスの属性の作り方には問題があります。
先ほどのような属性の書き方(name = ” health = 0 strength = 0 の部分)は、本来すべてのキャラクターに共通するような属性を記述する際に使用するものだからです。
これをクラス変数と呼びます。
例えば、キャラクターのパラメーターを定める際に、最大HPは999にしようとか、最大レベルは99にしようとか、そういった固定値を入れるのに使用します。
名前や現在のHPや攻撃力はキャラクターによって異なるわけですから、それらを上記のように一行一行改行しながら定めていくと、キャラクター作成に時間のかかる効率が悪いコードとなってしまいます。
より望ましい属性の定義
クラス変数を利用した属性の設定は、すべてのインスタンス(キャラクター)に共通する数値を設定するために使うべきという話をしました。
では、インスタンスごとに異なるパラメーターを設定するには、どうすればよいでしょうか。
__init__( )を使用した属性定義
ここで、__init__( )メソッドの出番です。
__init__( )はコンストラクタとも呼ばれています。
initは、initializeの略で、初期化という意味です。このため、初期化メソッドとも呼ばれます。
具体的な使い方は、下記の通りです。
class Character:
max_health = 999 # クラス変数
def __init__(self,name,health,strength):
self.name = name # インスタンス変数1
self.health = health # インスタンス変数2
self.strength = strength # インスタンス変数3
さきほど、クラス変数の使い方について触れましたので、最大HPを999に設定しています。
その下の、「def」から始まる部分が、インスタンスごとに違う値を定めることを前提とした属性の定め方です。
__init__に続いて、引数、すなわち()の中にselfを最初に書きます。
このselfというのは、各所では、よく「おまじない」とか、とりあえず書くものとされています。
その実態は、設定した各属性の初期値に何も入っていない空の「self」という名前のインスタンスを設定したということです。
いわば雛型となるインスタンスにまずselfという名前を付けるのがコードを書く際の決まりになっています。
selfに続いて__init__( )の引数を複数指定することにより、インスタンスが最初に持つ属性を複数定めることが出来ます。
その後、コード例のように self.name などの形で内容を定めるのが、インスタンス変数です。
再び、インスタンスの作成
では、インスタンスfrierenを作成してみましょう。
frieren = Character('Frieren',100,20)
これだけでインスタンスが出来ます。一行で済みますので、最初の書き方よりインスタンスを作るのが楽になったといえるでしょう。
わざわざクラスを作ったということは、その後、結構な数のインスタンスを作成する予定があるということです。
インスタンスを作成するのが楽という点でも、__init__は優れていると言えます。
__init__の分かりにくいところは、最初にメソッドとして使う時には「__init__(self,引数1,引数2,…)」と定めるのに、実際にインスタンスを作るときは、selfを飛ばして引数1から書くという点でしょう。
selfを除いて「インスタンス名 = クラス名(引数1,引数2,…)」と定めますので、同じ形じゃないのかと混乱する人が多いです。
再び、インスタンスの呼び出し
では、上記のコードをまとめてcharacter2.pyという名前で保存して、別ファイル(下記call.py)から呼び出してみます。
#%%
import character2
print(character2.frieren.name)
print(character2.frieren.health)
print(character2.frieren.strength)
dict_frieren = vars(character2.frieren)
print(dict_frieren)
print(character2.frieren.max_health)
先ほどの呼び出し方とほぼ変わりませんが、最後のコードが違います。
実行結果は以下のようになります。
frieren
100
20
{'name': 'Frieren', 'health': 100, 'strength': 20}
999
frierenの辞書には、max_healthがありません。
しかし、call.pyの最後の行のコードで、max_healthが定められており、呼び出すと出力できる状態であることを確認できます。
インスタンス作成練習
では、インスタンスを複数作ってみましょう。__init__を使用していますので、たった一行で次々作れます。
himmel = Character('Himmel',200,10)
eizen = Character('Eizen',200,10)
heiter = Character('Heiter',150,20)
あっという間に勇者一行が完成しました。上記のcharacter2.pyの方に追記して保存しましょう。
呼び出す際は、上記 frieren の呼び出し方と同じです。test_chara2.pyとでもして保存し、実行しましょう。
#%%
import character2
dict_himmel = vars(character2.himmel)
dict_eizen = vars(character2.eizen)
dict_heiter = vars(character2.heiter)
print(dict_himmel)
print(dict_eizen)
print(dict_heiter)
print(character2.himmel.max_Health)
print(character2.eizen.max_Health)
print(character2.heiter.max_Health)
出力結果は、以下のようになります。
{'name': 'Himmel', 'health': 200, 'strength': 10}
{'name': 'Eizen', 'health': 200, 'strength': 10}
{'name': 'Heiter', 'health': 150, 'strength': 20}
999
999
999
この作成練習で確認したいポイントは、
①__init__を使うと、楽にインスタンスを量産できること。
②クラス変数で設定した属性は、個別に設定せずともインスタンスに付与されていること
の2点です。
PR