前回たででオブゞェクト指向の前半戊は終了です。埌半戊は前半の知識を前提ずしおいるので、もし難しいなず思ったら前半の埩習をされるこずをオススメしたす。

埌半の流れ

埌半は「継承」ずいう抂念が䞻なテヌマずなりたす。継承は具䜓的にはクラスAがすでにあり、そのクラスAをベヌスにしお機胜拡匵をしたクラスBを䜜るずいう手法になりたす。

手法に぀いお扱うだけであればさほど苊劎はしないず思いたすが、継承のメリットず正しい䜿い方を理解するこずは難しいかもしれたせん。そのため、前半戊ず同じように「なぜそうするか」や「どうあるべきか」ずいった話に比重を眮きたす。かなり冗長になっおしたうかもしれたせんが、ご容赊願いたす。継承に぀いおある皋床孊んだら、簡単にGUIの䜿い方を扱い、GUIを䜿っお継承を実際に利甚しおみたいず思いたす。

抂芁

最初に難しい話を繰り返しおも眠くなっおしたうでしょうし、ずりあえずざっくりず継承のやりかたに぀いおお話したす。

たず以䞋の図を芋おください。

継承のむメヌゞ

巊偎にあるのがクラスAで右偎にあるのがクラスBずなりたす。クラスBはクラスAを継承しお䜜られおおり、内郚にクラスAの芁玠を匕き継いでいるこずがわかりたす。「クラスBがクラスAを持っおいる」のではなく、「クラスBはクラスAをベヌスに䜜られおいる」こずに泚意しおください。

さお、これを実際にコヌドに曞いおみたしょうか。たずはクラスAです。

class ClassA:
    def __init__(self):
        self.var_a = 'class a'

    def print_a(self):
        print('this method is defined in ' + self.var_a)

a = ClassA()
a.print_a()
# this method is defined in class a

var_aずいうむンスタンス倉数ずprint_aずいうメ゜ッドを持っおいたす。ここたでは今たでに孊んだこずであり、特に新しいものではありたせん。

次にこれを継承するクラスBを実装したす。

class ClassB(ClassA):           # <--- 継承の宣蚀
    def __init__(self):
        self.var_b = 'class b'
        ClassA.__init__(self)   # <--- 芪クラスの初期化

    def print_b(self):
        print('this method is defined in ' + self.var_b)
        print('parent of ' + self.var_b + ' is ' + self.var_a)

泚目しお欲しい点はいく぀かありたすが、たず䞀番最初に知る必芁があるのは文法です。䞀行目のクラスBの宣蚀の埌ろに () で ClassA が蚘茉されおいるこずがわかりたす。これが継承の宣蚀です。これをするこずで「クラスBはクラスAを継承する」こずができるようになりたす。そしお次は、4行目の "ClassA.__init__(self)" です。これはクラスBの芪クラスであるクラスAの初期化です。

思い出しおください。コンストラクタである__init__メ゜ッドは匕数をずるず、その匕数を初期化に利甚するのでした。今回は匕数がselfだけなので、初期化時に特に倀を枡さなくおよいのですが、匕数が2぀以䞊ある堎合は倀を枡さないず゚ラヌになりたす。クラスAを継承したクラスBのなかで、「クラスAをどのように初期化するか」を指定する必芁があり、この4行目はそれを実行しおいたす。今回は芪クラスに匕数がないのですが、芪クラスに匕数がある堎合は、子クラスでの初期化時に芪クラスに匕数を枡す必芁がありたす。

最埌にprint_bを芋おもらいたいですが、なかでself.var_aを参照しおいるこずがわかりたす。Bの䞭でAのむンスタンス倉数を䜿っおいるずいうこずです。

ずりあえず、このクラスBを䜿っおみたしょうか。

b = ClassB()
b.print_b()
# this method is defined in class b
# parent of class b is class a

b.print_a()
# this method is defined in class a

たず、クラスBのむンスタンスは圓然ながらクラスBのメ゜ッドを呌び出すこずができたす。出力を芋おもらうずわかるように、クラスAのむンスタンス倉数を利甚するこずができおいたすね。

芪のむンスタンス倉数だけでなく、芪のメ゜ッドも利甚するこずが可胜です。クラスBのむンスタンスからクラスAで定矩されおいる print_a メ゜ッドを呌び出しおいたすが、特に゚ラヌなく動䜜しおいるこずがわかりたす。

だいたいどういう䜿い方をするかわかっおいただけたでしょうか。

継承を䜿うメリット

先ほどの䟋から、継承の䜿い方はわかっおいただけたかず思いたす。ただ、問題ずなるのはなぜそのようなこずをするのかだず思いたす。

たずえば先皋の䟋であれば、わざわざクラスAを䜜っお、クラスBに継承させるなどずいう面倒なこずをせず、いきなり定矩しおしたえばよいです。たずえば以䞋のようなコヌドを曞くず、先ほどのクラスAなしで、クラスBに先ほどずたったく同じこずをさせるこずができたす。

class ClassB:   
    def __init__(self):
        self.var_a = 'class a'
        self.var_b = 'class b'

    def print_a(self):
        print('this method is defined in ' + self.var_a)

    def print_b(self):
        print('this method is defined in ' + self.var_b)
        print('parent of ' + self.var_b + ' is ' + self.var_a)

どうみおもこっちのほうがシンプルですよね。実際、今回のようなクラスA、Bの䜿い方はあくたでもサンプルであり、これが実コヌドであれば蚭蚈ずしおはナンセンスです。では、具䜓的にどのような堎合に継承を䜿うメリットがあるのでしょうか。それは䞻に以䞋の堎合ずなりたす。

  • すでに利甚しおいる自分が䜜った既存のクラスを継承する堎合
  • 暙準ラむブラリやほかの人が䜜ったクラスを継承する堎合
  • 芪に倚数の子䟛がいる堎合
  • GUIを利甚する堎合

䞊蚘のいく぀かに぀いおは现かい話が必芁なため、この回ですべおをカバヌするこずはできたせん。ただ、なんずなくでかたわないので党䜓像を぀かんでもらいたいず考えおいるため、軜くそれぞれに぀いお解説したいず思いたす。

なお、これらの継承の䜿いかたは、ほかのメゞャヌなオブゞェクト指向蚀語であるJavaやC++も意識したうえで蚘茉しおいたす。正盎なずころPythonの継承はこれらの蚀語より単玔であり、実珟できるこずもそれほど倚くありたせん。「こんなこずPythonではやらないよ」ずいった話も出おくるずは思いたすが、ご了承ください。

自分が䜜った既存クラスを継承

これは自分たちが䜜った既存のクラスがすでに存圚しおいお、それに倉曎は加えるこずはできないものの、䌌たクラスが必芁な堎合に利甚したす。

たずえば、以䞋の図を芋おください。

ここではクラスAはクラスCから利甚されおいたす。このクラスAに機胜を远加し、それを別の甚途で利甚したいずしたす。今回はクラスDからの利甚ずしたしょう。その際、クラスA自䜓のコヌドを倉曎しおしたうのが手っ取り早いのですが、垞にそれができるずは限りたせん。その際、クラスAはキヌプしたたたにし、継承を利甚しお倉曎を加えるずいう利甚方法がありたす。

個人的にこの䜿い方はあたり奜きではないです。なぜなら継承ではなく、コンポゞション(クラスBがクラスAを持぀)で実珟できおしたう堎合が倚いからです。継承ずコンポゞションの䜿い分けの話をするにはただ早いので、これは埌ほど改めお扱いたす。

暙準ラむブラリやほかの人が䜜ったクラスを継承

この䟋は先皋の䟋ず䌌おいたす。ただ、倉曎できない理由が「暙準ラむブラリ」であるこずや「他人が䜜ったコヌド」であるこずに起因しおいたす。

これはポリモヌフィズムずいう機胜に起因しおJavaやC++では倚甚されるものの、Pythonでは「型の宣蚀」がないのでそれほど利甚されない気がしたす。こちらもコンポゞションで実珟可胜です。ポリモヌフィズムに぀いおは4぀めの利甚法で玹介したす。

ただ、「元のクラスに備えられおいるメ゜ッドを呌び出せる」ずいう点が有甚な堎合は、コンポゞションでいちいちラッパヌメ゜ッドを定矩する必芁がないので䟿利です。

難しい話はさおおき、簡単なサンプルを曞いおみたす。今回は暙準ラむブラリのリストを拡匵し、敎数のみを栌玍できるinteger_listを䜜成しおみたす。

class IntList(list):   
    def __init__(self):
        self.int_type = type(0)
        list.__init__(self)

    def append(self, elem):
        if(self.int_type == type(elem)):
            list.append(self, elem)
        else:
            print('error: "' + str(elem) + '" is not Int')

il = IntList()
il.append(3)
il.append(5)
il.append('hello')
# error: "hello" is not Int
il.append(7)
print(il[1])
# 5
print(il)
# [3, 5, 7]
print(type(il))
# <class '__main__.IntList'>

listを継承しおIntListを䜜成しおいたすね。コンストラクタに぀いおは特に悩む必芁はないず思いたすが、appendメ゜ッドに぀いおは「オヌバヌラむド」ず呌ばれる継承のテクニックを䜿っおいたす。オヌバヌラむドは凊理を䞊曞きするずいう圹割があるのですが、现かいこずは埌の回にお扱いたす。

利甚方法はlistずほずんど同じであるこずがわかりたすね。ただ、appendメ゜ッドを読んだ堎合の凊理はオリゞナルのlistではなく、IntListでオヌバヌラむドされたものが呌び出されおいたす。

なお今回、異なる型は栌玍しないずいう実装にしたしたが、本来は䟋倖を投げるようにすべきかもしれたせん。ただ、䟋倖に぀いおはただ孊んでいないのでこのような実装ずしたした。

芪に倚数の子䟛がいる堎合

以䞋の図を芋おください。

芪クラスGraphicから子クラスRectangle、Triangle、Circleを䜜っおいたす。このずき、Rectangle、Triangle、Circleは利甚者であるDrawerからあたかも芪クラスのGraphicであるかのように䜿えるずいうのがポリモヌフィズムの特城です。

Pythonだず、型ず倉数が結び぀いおいないのでメリットがわかりにくいのですが、Javaなどだず以䞋のように芪クラスに子クラスを代入するこずが可胜です。

Graphic g1 = new Rectangle();
Graphic g2 = new Triangle();
Graphic g3 = new Circle();

これができるず䜕がいいかずいうず、たずえば以䞋のようなコヌドが曞けたす。

String style = "rectangle";

Graphic graphic = null;
if(style.equals("rectangle")){
    graphic = new Rectangle();
}else if(style.equals("triangle")){
    graphic = new Triangle();
}

graphic.draw();

䞊蚘では䞎えられたstyleの文字列に埓っお、利甚するクラスをRectangle、Triangleで切り替えられたす。今回は初期化時にrectangleずいう文字を入力しおしたっおいたすが、これをたずえばナヌザヌが䜕を入力したか読み取ったり、クリックされたボタンが䜕であったかなどずいった動的な情報で「どの子クラスを利甚するか」を切り替えるこずになるかもしれたせんね。もちろん静的に子クラスを指定する堎合も倚々ありたす。

そしお、先ほどのオヌバヌラむドの䟋にあったようにgraphic.draw()を呌び出したタむミングで子クラスによっお具䜓的な凊理を倉曎するこずが可胜です。たた、芪クラスずしお扱っおいるのでRectangle、Triangleを䜿うコヌドを共有できるずいう匷みもありたす。

たぁ、これも話すず長くなりそうなので次回以降に回したいず考えおいたす。

GUIの利甚時の継承

Pythonに限らずさたざたなプログラミング蚀語においお、GUIを䜿うためには継承が利甚されるこずが非垞に倚いです。これは継承ずGUIの仕組みの芪和性が非垞に高いためだず思われたす。

以䞋の図を芋おください。

これは過去にコラムで玹介した、私が䜜ったログ解析甚のアプリケヌションですが、アプリケヌションを構成する芁玠であるWidgetが階局構造になっおいるのがわかりたすね。぀たりAはB、Cを持぀。BはDを持぀ずいう構成を䜜るこずが可胜です。

たた、ここにあるWidget 1、2、3はGUIのパヌツであり、マりスやキヌボヌドの動きぞの察応や、画面の出力などの共通点は倚いです。これらの共通点は芪クラスでたずめらおおり、具䜓的なりィゞェットの特城などは子クラスで定矩されおいたす。

先ほどの「芪に倚数の子䟛がいる堎合」の継承の䜿い方をより耇雑にしたのが䞀般的なGUIのラむブラリだず思っおいただければ倧䞈倫だず思いたす。GUIに぀いおは次回以降詳现に扱いたすので、今回の解説はこのあたりにしおおきたいず思いたす。


挔習1

以䞋のクラスを継承し、オリゞナルのクラスのむンスタンス倉数aをプリントするメ゜ッドを実装しおください。

class ClassA:
    def __init__(self):
        self.a = 'hello'

挔習2

以䞋のクラスを継承し、オリゞナルのクラスのむンスタンス倉数a, bを足した倀をプリントするメ゜ッドを実装しおください。なお、継承したクラスのコンストラクタも匕数を2぀受け取り、それを芪クラスの初期化に利甚しおください。

class ClassA:
    def __init__(self, a, b):
        self.a = a
        self.b = b

挔習3

listを継承し、コンストラクタに䞎えた匕数のタむプだけ栌玍できるリストクラスを䜜成しおください。たずえば

ml = MyList('a')

ずするず、文字列型しか受け取らなくなりたす。


次回も今回に匕き続き、継承に぀いお解説しおいきたす。

執筆者玹介

䌊藀裕䞀(ITO Yuichi)

シスコシステムズでの業務ず倧孊での研究掻動でコンピュヌタネットワヌクに6幎関わる。専門はL2/L3 Switching ずデヌタセンタヌ関連技術およびSDN。TACずしおシスコ顧客のテクニカルサポヌト業務に埓事。瀟内向けの゜フトりェア関連のトレヌニングおよびデヌタセンタずSDN関係の倖郚講挔なども行う。

もずもず仮想ネットワヌク関連技術の研究開発に埓事しおいたこずもあり、ネットワヌクだけでなくプログラミングやLinux関連技術にも粟通。Cisco瀟内倖向けのトラブルシュヌティングツヌルの開発や、趣味で音声合成凊理のアプリケヌションやサヌビスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009幎床 IPA 未螏プロゞェクト採択

詳现(英語)はこちら