LoginSignup
65
71

More than 3 years have passed since last update.

Pythonの継承とAbstract Base Class

Last updated at Posted at 2015-01-31

pythonのバージョンを新しくして実行したところ、挙動が変わっていたので補足を末尾に追記しました(2019/1/6)

pythonで同じようなクラスを作ることになったので、Interfaceだったり抽象クラスだったりを使って実装してみたいと思って調べました。
で、調べてみたところ、Abstract Base Classというものが存在していることを知りました。
ふむふむ。
実は継承してクラスを作ったことがないので、それと比較してみました。

まずは、普通の継承。

Parent.py
class Parent(object):

    def __init__(self, name):
        self.name = name  # self.__nameだとChildから呼べない

    def explain(self):
        print(self.name)
Child.py
from Parent import *

class Child(Parent):

    def __init__(self, name, age):
        super().__init__(name)
        self.__age = age

    def hello(self):
        print('Hello ' + self.name, self.__age)

これをmain.pyから呼びます。

main.py
from Child import *

if __name__ == "__main__":
    parent = Parent('Bob')
    parent.explain()  # Bob

    child = Child('Bob Jr.', 6)
    child.explain()  # Bob Jr.
    child.hello()  # Hello Bob Jr. 6

コメントにもありますが、親クラスの属性がprivateだと、子クラスから参照できませんでした。
知らなかった・・・pythonにはprotectedな変数を作ることができないので、publicにする必要があります。

次はAbstract Base Classを使ってみます。

Abstract.py
from abc import *

class Abstract(object):
    __metaclass__ = ABCMeta

    def __init__(self, name):
        self.name = self.uppercase(name)  # self.__nameだとImplementから呼べないのは継承と同じ

    @classmethod
    @abstractmethod
    def uppercase(cls, name):  # __uppercase(cls, name):だと、Implementクラスで実装しても、Abstractクラスのメソッドが呼ばれる
        raise NotImplementedError()

    def explain(self):
        print(self.name)

    @abstractmethod
    def hello(self):
        raise NotImplementedError()

当然、上記のクラスを直接objectにすることはできません。
できないのですが、それはuppercaseメソッドがNotImplementedErrorとなっているからであって、実はそこを実装してしまえばobject自体は作れてしまいます。
で、実装クラスはこちら。

Implement.py
from Abstract import *

class Implement(Abstract):

    @classmethod
    def uppercase(cls, name):
        return name.upper() + ' by Impl'

    def hello(self):
        print('Hello ' + self.name)

これも、実はhelloメソッドを実装しなくても動きます。
その場合、helloメソッドを呼ぶと、Abstractクラスのhelloメソッドが呼ばれます(今回はNotImplementedErrorとなる)。

うーん、想像していた抽象クラスとは違う・・・(´・ω・`)
一応、PyCharmでコードを書くと、abstractmethodの実装がない場合は警告が出ますが、個人的にはAbstractクラスはobject作る時点でエラーになってほしいし、Implementクラスも、未実装のabstractmethodがある場合はエラーになってほしいのですが・・・。
一応、main.pyから呼ぶと、こんな感じです。

main.py
from Implement import *

if __name__ == "__main__":
    impl = Implement('John')
    impl.explain()  # JOHN by Impl
    impl.hello()  # Hello JOHN by Impl

でも、いい勉強になりました。

追記(2019/1/6)

python3.6.7 で再実行したところ、挙動が変わっていました。

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    def __init__(self, name):
        self.name = name

    @classmethod
    @abstractmethod
    def uppercase(cls, name):
        raise NotImplementedError()

    def explain(self):
        print(self.name)

    @abstractmethod
    def hello(self):
        raise NotImplementedError()


class Implement(Abstract):
    @classmethod
    def uppercase(cls, name):
        return name.upper() + ' by Impl'

class 作る際に __metaclass__ ではなく、直接 ABCMeta を metaclass と指定して継承すると、Abstract を object 化する際に、

abs = Abstract('Bob')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods hello, uppercase

となり、Implement も

Implement('Dan')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Implement with abstract methods hello

となり、エラーが出るようになってました。
これでより安全にプログラミングができますね!

@ikeyan314 さんありがとうございます!!

65
71
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
71