オブジェクト指向とオブジェクト指向プログラミングは現代のプログラミングでは理解必須の概念です。
Pythonのプログラムにおけるデータは全て、オブジェクトまたはオブジェクト間の関係として表されます。
これまで機能を説明してきた文字列やリストも全てオブジェクトです。
しかしオブジェクト指向について「平易に」かつ「正確に」説明することをこのページでは行いません。
そのため文字列やリストの説明でも、オブジェクト(とメソッド)についての部分は敢えて説明してきませんでした。
ここから先の説明はオブジェクト指向を理解している前提でPythonにおけるクラスやオブジェクトの使用方法を説明します。
クラス
実際にクラスとオブジェクトを使用したプログラムを以下に記述しました。実行して動作を確認してください。
class Spam:
val = 100
def ham(self):
self.egg('call method')
def egg(self,msg):
print("{0}".format(msg))
print(("{0}".format(self.val)))
spam = Spam()
spam.ham()
結果は以下になります。
call method
100
Pythonではクラスはclass クラス名 **:**で作成します。クラス名の先頭は大文字です。
クラス変数とクラス内関数(=メソッド)は字下げを使用して定義します。
オブジェクトは オブジェクト = クラス() で生成します。
メソッドは *オブジェクト.メソッド()*で呼び出します。
上記のプログラムを日本語で説明すると
- クラスSpamのオブジェクトspamを生成します
- spamオブジェクトがhamメソッドを呼び出します
- spamオブジェクトのhamメソッドはspamオブジェクト(=自身)のeggメソッドを呼び出します
- spamオブジェクトのeggメソッドは引数のmsgを出力します
- eggメソッドはspamオブジェクトの変数valを出力します
ポイントはメソッドの最初の引数selfです。他の言語からPythonに入った人は
「spam.ham()には引数が無いのに、このselfはなんなのだろう?」と思うかもしれません。
Pythonではメソッドは最低1つの引数を持ちます。この最初の引数は必ずselfという名前にする慣例があります。
selfを使用することで、オブジェクト自身の変数を取得したり、メソッドを呼び出すことが出来ます。
Javaで言う「this」です。
コンストラクタ
オブジェクト生成時に呼び出される特殊な関数をコンストラクタと言います。
オブジェクトが扱うデータの初期化などをここで行います。
コンストラクタは__init__()
という名前で定義します。"_
"を前後に2つです。注意してください。
class Spam:
def __init__(self,ham,egg):
self.ham = ham
self.egg = egg
def output(self):
sum = self.ham + self.egg
print("{0}".format(sum))
spam = Spam(5,10)
spam.output()
結果は以下になります。
15
デストラクタ
コンストラクタの逆で、オブジェクトが不要となりPythonが削除する時に自動で実行される関数です。
__del__
という名前のメソッドで定義します。しかしほとんどの場合デストラクタは定義しません。理由として
- 仕様上、プロセス終了までにデストラクタが必ず呼び出される保証がない
- 複数のオブジェクトが相互作用する場合、どのオブジェクトのデストラクタを先に実行するか決定できないため挙動が怪しくなる
リソースの開放にはwithを使用しますが、レベルを越えるためここでは説明しません。
継承
「継承」はあるクラスを別のクラスのデータとメソッドを持つクラスとして定義することです。
継承はPythonのみならず、オブジェクト指向プログラミング言語に共通する重要な考え方です。
以下のプログラムを実行してください。
class Base:
basevalue = "base"
def spam(self):
print("Base.spam()")
def ham(self):
print("ham")
class Derived(Base):
def spam(self):
print ("Derived.spam()")
self.ham()
derived = Derived()
print("{0}".format(derived.basevalue))
derived.ham()
class クラス名(基底クラス名) : で基底クラスの変数とメソッドを引き継ぐクラス(派生クラス)を定義できます。
派生クラスは基底クラスの変数とメソッドを継承しているため、出力は以下のようになります。
base
ham
多重継承
Pythonは複数の基底クラスを引き継ぐ「多重継承」が可能です。
class クラス名(基底クラス名1,基底クラス名2,・・・)
class A:
def method(self):
print("class A")
class B:
def method(self):
print("class B")
class C(A):
def method(self):
print("class C")
class D(B,C):
pass
d = D()
d.method()
出力結果はclass Bになります。
めったに使用しませんが、クラス内にpassと記述すると何もしないクラスを作成できます。
クラスDのオブジェクトdのメソッドmethod()を呼び出すと、クラスDの基底クラスBとCのうち、
先に記述されているクラスBのメソッドmethod()が呼び出され、class Bが出力されます。
上記のプログラムのクラスBを、クラスDと同様passのみ記述すると、基底クラスBにはメソッドmethod()が存在しないため
クラスCのメソッドmethod()が呼び出され、出力結果はclass Cになります。
クラスCもpassのみ記述すると、クラスCの基底クラスであるクラスAのメソッドmethod()が呼び出され、
class Aが出力されます。
クラスAをpassのみ記述すると、いずれのクラスにもmethod()が存在しないので例外が発生します。
複雑な多重継承を作るとメソッドの検索順序を決定できないため、エラーになることがあります。注意してください。
privateとpublic
オブジェクト指向プログラミングで重要な要素であるカプセル化。
Pythonでは変数やメソッドの名前の前に"__"(_を2つ)をつけることによりprivateにできます。
以下のプログラムを実行してください。
class Spam:
__attr = 100
def __init__(self):
self.__attr = 999
def method(self):
self.__method()
def __method(self):
print(self.__attr)
spam =Spam()
spam.method() #OK
spam.__method() #NG
spam.__attr #NG
spam.method()の行は正常に実行されますが、spam.__method()の行でエラーが発生します。
クラスSpamのメソッドmethodは先頭に"__"が付いていないことからpublicです。
オブジェクトから直接呼び出すことが出来ます。
methodメソッド内で先頭に"__"の付いている__method()を呼び出していますが、
内部からの参照のためエラーにはなりません。
しかし、spamオブジェクトが直接__method()を呼び出そうとした場合、
先頭に"__"が付いている__method()はprivateなため呼び出すことが出来ず、エラーとなります。
これはspam.__attrも同様です。