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…
!