LoginSignup
60
47

More than 3 years have passed since last update.

Pythonの抽象クラス(ABCmeta)を詳しく説明したい

Last updated at Posted at 2020-09-26

0.はじめに

Pythonの抽象クラスが思いのほか特殊だったので
メタクラスの説明も踏まえて少しばかり丁寧に解説したい

1.ABCmetaの基本的な使い方

PythonのAbstract(抽象クラス)は少し特殊で、メタクラスと呼ばれるものに
ABCmetaを指定してクラスを定義する(メタクラスについては後ほど説明)

from abc import ABC, ABCMeta, abstractmethod

class Person(metaclass = ABCMeta):
    pass

ちなみにABCクラスというのもあってこちらはmetaclassを指定せず
継承するだけなので基本こっちの方が分かりやすい

class Person(ABC):
    pass

抽象化したいメソッドに@abstractmethodを付ける

@abstractmethod
def greeting(self):
    pass

継承したクラスで実装する

class Yamada(Person):

    def greeting(self):
        print("こんにちわ、山田です。")

もちろん実装しないで、インスタンス化するとエラー

class Yamada(Person):
    pass

yamada = Yamada()
---------------------------------------------------------------------------------------------------------------
TypeError: Can't instantiate abstract class Yamada with abstract methods greeting

以上が、基本的な抽象クラスの使い方

1.1そもそもメタクラスとはなんなのか

ここから少しそれてメタクラスの話、興味なかったら飛ばしてください

メタクラスとは

オブジェクト指向プログラミングにおいてメタクラスとは、インスタンスがクラスとなるクラスのことである。通常のクラスがそのインスタンスの振る舞いを定義するように、メタクラスはそのインスタンスであるクラスを、そして更にそのクラスのインスタンスの振る舞いを定義する。

【出典】: メタクラス - wikipedia

とまあよくわからないので、実例を出すと
実は、Pythonの隠し機能(?)でtypeからクラスを動的に定義できてしまう

def test_init(self):
    pass

Test_Class = type('Test_Class', (object, ), dict(__init__ = test_init, ))

もちろん、このクラスをインスタンス化できる

Test_Class_instance = Test_Class()
print(type(Test_Class_instance))
-------------------------------------------------------------------------------------------------------------------
<class '__main__.Test_Class'>

ようするに先ほどtypeで生成したTest_Classは、インスタンスともクラスの定義ともいえる
これが、狭義的な意味でのメタクラスになる

驚きなのは、Pythonにおいて普段何気なく定義しているclassは
type型のインスタンスだったりするのだ

class define_class:
    pass

print(type(define_class))
-------------------------------------------------------------------------------------------------------------------
<class 'type'>

結局のところ、Pythonはclassを定義すると内部で
type('classname', ...)が自動で呼び出されるという仕組みになっている
ということになる

この自動で呼び出せれるクラスを変えてしまえという
超強力な機能が__metaclass__ の正体で、
ABCmetaというのはtypeを継承したクラスということになる
ABCmetaのソースコード

ちなみに、metaclassは強力すぎて言語の仕様自体変えてしまう
黒魔術のため乱用しないほうがいいです

2.classmethod、staticmethod、propertyの抽象化

@abstractclassmethod@abstractstaticmethod@abstractproperty
がともに非推奨になってしまったので、デコレータを重ねる必要がある

抽象クラス

class Person(ABC):

    @staticmethod
    @abstractmethod
    def age_fudging(age):
        pass

    @classmethod
    @abstractmethod
    def weight_fudging(cls, weight):
        pass

    @property
    @abstractmethod
    def age(self):
        pass

    @age.setter
    @abstractmethod
    def age(self, val):
        pass

    @property
    @abstractmethod
    def weight(self):
        pass

    @weight.setter
    @abstractmethod
    def weight(self, val):
        pass

実装

class Yamada(Person):

    def __init__(self):
        self.__age = 30
        self.__weight = 120

    #10歳サバ読み
    @staticmethod
    def age_fudging(age):
        return age - 10

    #20kgサバ読み
    @classmethod
    def weight_fudging(cls, weight):
        return weight - 20

    @property
    def age(self):
        return Yamada.age_fudging(self.__age)

    @age.setter
    def age(self):
        return

    @property
    def weight(self):
        return self.weight_fudging(self.__weight)

    @weight.setter
    def weight(self):
        return

y = Yamada()
print("名前:山田 年齢:{0} 体重:{1}".format(y.age, y.weight))
-----------------------------------------------------------------------------------------------------------------
名前:山田 年齢:20 体重:100

多少長くなってしまったが、基本的な使い方は同じ
ただ、@abstractmethodが下に来るようにしないとエラーを吐くので注意

ちなみに、Pythonではメンバ変数の前に「__」を付けるとプライベート化できます

3.抽象クラスの多重継承

Pythonでは多重継承が使えるが、抽象クラスではどうでしょう

ご存じの通り、多重継承はいろいろと問題があるので(菱形継承問題、名前衝突)
基本的に多重継承は使わない方が無難だが、使う場合はMixinなクラスであることが求められる

3.1Mixinとは?

mixin とはオブジェクト指向プログラミング言語において、
サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラスである。言語によっては、
その言語でクラスや継承と呼ぶものとは別のシステムとして mixin がある場合もある(#バリエーションの節で詳述)。

【出典】: Mixin - wikipedia

またWikipediaからの引用だが要するに
継承しないとまともに動かないクラスのことで
抽象クラスやインターフェイスもMixinなクラスといえる

ということで、猫と人間を継承した獣人クラスを作っていく

class Cat(ABC):

    @abstractmethod
    def mew(self):
        pass

    #Personにもある
    @abstractmethod
    def sleep(self):
        pass

class Person(ABC):

    @abstractmethod
    def greeting(self):
        pass

    #Catにもある
    @abstractmethod
    def sleep(self):
        pass

class Therianthrope(Person, Cat):

    def greeting(self):
        print("こんにちわ")

    def mew(self):
        print("にゃー")

    def sleep(self):
        print("zzz…")

cat_human = Therianthrope()

cat_human.greeting()
cat_human.mew()
cat_human.sleep()
-----------------------------------------------------------------------------------------------------------------
こんにちわ
にゃー
zzz

ABCmetaでも難なく動作することがわかる
今回は、sleepというメソッドを名前衝突させているが
継承元で実装されているわけではないので、問題がない

最後にPythonの公式サイトにも書いてある通り
メタクラス同士の衝突には十分注意したほうがいい

4.抽象クラスの多段継承

多段継承で懸念になるのはABCmetaは継承すると引き継ぐのか
言い換えると、Pythonは抽象クラスを継承すると抽象クラスに
なってしまうのかという点

結論から言うとABCmetaは引き継ぐし、抽象クラスを継承すると抽象クラスになります

class Animal(metaclass=ABCMeta):
    pass

class Person(Animal):
    pass

class Yamada(Person):
    pass

print(type(Person))
print(type(Yamada))
-------------------------------------------------------------------------------------------------------------------
<class 'abc.ABCMeta'>
<class 'abc.ABCMeta'>

つまるところ抽象メソッドはどこかしらで
実装すればよいということになる

class Animal(metaclass=ABCMeta):

    @abstractmethod
    def run(self):
        pass


class Person(Animal):
    pass

class Yamada(Person):

    def run(self):
        print("時速12km")

y = Yamada()
y.run()
-------------------------------------------------------------------------------------------------------------------
時速12km

5.仮引数に抽象クラスをとる

だいぶ長くなってしまったが、最後に抽象クラスのざっくりとした
Pythonでも使える使用法の一例を紹介

実は、Pythonでも仮引数、戻り値の型を指定することができるので
先ほど作った親クラスであり抽象クラスでもある「Person」を仮引数の型に指定した
fall_asleepという関数を作っていきましょう

from abstract import Person
import time

def fall_asleep(p:Person):
    sleep_time = p.sleep()
    time.sleep(sleep_time)
    print("!")
class Person(ABC):

    @abstractmethod
    def greeting(self):
        pass

    @abstractmethod
    def sleep(self) -> float:
        pass

抽象クラスやら型指定やらと、もはやPythonではなくなって来てますが
この様なテクニックは、Pythonで少し規模が大きいソフトフェアを作るとき
なんかに役に立ちます。

例えばfall_asleepを作っている人は、具象クラスの実態(YamadaなのかIkedaなのかYosidaなのか)を
気にせず(知らず)、またいくら変更しようともsleepという関数が要件さえ満たしてれば
完成させられるので、fall_asleepは具象クラスではなく抽象クラス(Person)に依存しているといえる

このことをアーキテクチャでは「依存関係逆転の原則(DIP)」なんて
言ったりします

DIPは以下の物が該当

  • (変化しやすい)具象クラスを参照しない
  • (変化しやすい)具象クラスを継承しない
  • 具象関数をオーバーライドしない

結局のところ抽象クラスに依存したほうが、
強固なアーキテクチャを実現できるというわけです

y = Yamada()
fall_asleep(y)
-------------------------------------------------------------------------------------------------------------------
zzz
!
60
47
0

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
60
47