親クラスにgetterだけ実装して、子クラスにはsetterみたいなのだけ実装したい
親クラスと子クラス両方で使う変数には「__(アンダーバー2つ)」付けずに実装するようです。
「__」を付けてしまうと、privateな扱いになってしまい、別のクラスからアクセス出来なくなってしまいます。
これでは、子クラス内の変数が隠蔽されません。
これを解消したく色々試した結果、以下の2つのコードになりました。
コード1 「_」を1つにする、また親クラスのsetterで型を調べる
from abc import ABCMeta, abstractmethod
# 抽象クラス
class Animal(metaclass=ABCMeta):
@abstractmethod
def __init__(self):
raise NotImplementedError()
@property
def voice(self):
return self._voice
@voice.setter
def voice(self, voice):
if not isinstance(voice, str):
raise TypeError(voice)
self._voice = voice
# 抽象クラスを継承
class Dog(Animal):
def __init__(self, voice="woof"):
self.voice = voice
if __name__ == "__main__":
print(issubclass(Dog, Animal)) # True
print(isinstance(Dog(), Animal)) # True
dog = Dog()
print(dog.voice) # woof
dog.voice = 123 # TypeError
コード1解説
アンダーバーを1つにして、隠蔽性をなくしました。
そもそもPythonはアンダーバーが付いている変数へアクセスする方が悪いという、暗黙の紳士協定が存在する言語です。
プログラムの可読性やら簡単さを考えるとこれが一番かなと思います。
この説明とコードは@siracamusさんのコメントよりいただきました。
ありがとうございます。
コード2 親クラスのsetterで呼び出し元が子クラスか確認する
from abc import ABCMeta, abstractmethod
import inspect
class Animal(metaclass=ABCMeta):
@abstractmethod
def __init__(self):
raise NotImplementedError()
@property
def voice(self):
return self.__voice
@voice.setter
def voice(self, voice):
frame = inspect.currentframe().f_back
assert (inspect.getframeinfo(frame)[2] not in dir(self.__class__)), \
"unexpected assignment: voice"
self.__voice = voice
def get_voice(self):
return self.__voice
# 抽象クラスを継承
class Dog(Animal):
def __init__(self):
self.set_voice("woof")
def set_voice(self, voice: str):
self.voice = voice
if __name__ == "__main__":
print(issubclass(Dog().__class__, Animal)) #true
print(isinstance(Dog(), Animal)) #true
dog = Dog()
print(dog.get_voice()) #woof
dog.voice = "aaa" # error
print(dog.voice) # 実行可能
コード2の説明
inspectを利用することにより、親クラスのsetterへのアクセス元が、子クラスのメンバ関数か調べています。
ちょっとメタっぽいプログラムになってしまいました。
子クラス側のコード量自体は減ったと思います。
感想
自分はアンダーバー2つをよく使ってたのですが、一般的には使わないみたいですね。
inspectの存在を知らなかったのですが、これを使うとかなり色々できそうですね。
参考にしたページ
https://qiita.com/kaneshin/items/269bc5f156d86f8a91c4
https://docs.python.org/ja/3/library/inspect.html