0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonの親クラスにgetter、子クラスにsetterを実装する。

Last updated at Posted at 2017-10-21

親クラスに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

0
1
5

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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?