クラスでプロパティを使う - Python
入門 Python 3 に記載されていたプロパティについてまとめてみた。忘れてもいいようにメモとして残しておく。
Pythonではすべての属性、メソッドが公開となっている。もし、属性を非公開をしたいときにはプロパティという機能を使う。まずはproperty()メソッドを使った実装方法を見てみる。
propertyメソッド
例として、hidden_nameという属性を持つPersonクラスを定義する。hidden_nameは外部から直接アクセスしないようにしたいとする。
class Person(): def __init__(self, input_name): print('get_name!') self.hidden_name = input_name def get_name(self): print('get_name!') return self.hidden_name def set_name(self, input_name): self.hidden_name = input_name name = property(get_name, set_name)
name = property(get_name, set_name)
という行で「get_name()をnameというプロパティのgetter」として、「set_name()をnameというプロパティのsetter」として定義されている。propertyメソッドの第一引数にgetter、第二引数にsetterを渡す。
実際に使ってみる。まずはgetterから。
>>> person = Person('taro') >>> person.name get_name! 'taro' >>> person.get_name() get_name! 'taro'
nameプロパティにアクセスすると、get_name()が呼び出されるようになっている。また、get_name()を直接呼び出すこともできる。
次はsetter
>>> person.name = 'jiro' set_name! >>> person.name get_name! 'jiro' >>> person.set_name('saburo') set_name! >>> person.name get_name! 'saburo'
getterと同様に、nameプロパティに代入をすると、set_name()が呼び出されている。また、直接set_name()を呼び出すこともできる。次に、デコレータを使った実装方法を見てみる。
デコレータ
name = property(get_name, set_name)と記述していた部分はデコレータでも記述できる。同じメソッド名に@property
と@プロパティ名.setter
というデコレータをつける
@property
:getterのメソッドにつける
@プロパティ名.setter
:setterのメソッドにつける。例)name.setter
デコレータを使ってプロパティを定義
class Person(): def __init__(self, input_name): self.hidden_name = input_name @property def name(self): print('get_name!') return self.hidden_name @name.setter def name(self, input_name): print('set_name!') self.hidden_name = input_name
使ってみる
>>> from main import Person >>> person = Person('tata') >>> person.name get_name! 'tata' >>> person.name = 'kaka' set_name! >>> person.name get_name! 'kaka'
get_name()
とset_name()
が定義されていない。また、hidden_name
には外部から直接アクセスできる。それについてはまたあとで。
また、プロパティは必ずしも属性の値を返さなくても良い。計算した結果を返しても良い。以下のようにCircle
(円)クラスにradius
(半径)という属性とdiameter
(直径)というプロパティを定義する。
# 円クラス class Circle(): def __init__(self, radius): self.radius = radius @property def diameter(self): return 2 * self.radius
>>> c = Circle(5) >>> c.radius 5 >>> c.diameter 10
radius
の値を元に結果を返している。
diameter
はその時のradius
の値を元に結果を返しているため、変更した場合には違う値が返ってくる。
>>> c.radius = 4 >>> c.radius 4 >>> c.diameter 8
プロパティのsetterを定義しなかった場合、それは読み取り専用のプロパティになる。
プロパティを使うことで、プロパティのメソッド(@property
、@xxx.setter
のメソッド)内の処理を変えても呼び出す側のソースは変える必要がなくなる。
非公開な属性
プロパティを使うだけでは外部からは直接アクセスできないようにはなっていない。Pythonには属性を非公開にするための命名規則がある。
非公開にしたい属性の変数名の先頭にアンダーバーを2つつける(__
)だけでよい。Person
クラスのhidden_name
属性を非公開属性に変えてみる。
class Person(): def __init__(self, input_name): self.__name = input_name @property def name(self): print('get_name!') return self.__name @name.setter def name(self, input_name): print('set_name!') self.__name = input_name
>>> person = Person('taro') >>> person.name get_name! 'taro' >>> person.name = 'jiro' set_name! >>> person.name get_name! 'jiro'
前と同じように属性にアクセスすることはできている。しかし、__name
に直接アクセスはできなくなっている。
>>> person.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Person' object has no attribute '__name'
完全には非公開にはなっていない。Pythonの機能のマングリング(ぐちゃぐちゃに変形すること)によって偶然直接呼び出してしまわないようになっている。マングリング後には次のようになっている。
>>> person._Person__name
'jiro'
getterとして呼び出していないということがわかる(get_name!
と出力されていない)。完全ではないが、簡単には直接アクセスできないようになっている。
以上。
また、プロパティについて何か新しい発見があった場合にはここに追記していく。
2017/10/9 追記
Pythonでの日付について学習中にdatetimeモジュールのtimeクラスのソースを見ていたときに、プロパティを使っていたため、どのように使っていたかをメモしておく。
読み取り専用として使っていた。
Pythonのdatetime.timeのソースでのプロパティの記述
@hour.setter
がついたhour()
メソッドがないため読み取り専用として扱われる。
あと、Pythonのソースがデコレータを使っているからデコレータを使うのがPython的な書き方なのかも?
また、self._hour
の部分でアンダースコアが一つになっているのが少し気になった。アンダースコアが1つしかついていない属性は習慣的に参照しないということらしい。また、アンダースコアが2つ前についている属性は完全に参照できなくなる。ということらしい。「1つは習慣的に参照しない」、「2つは文法的に参照できない」ということ!!
参考文献
【備忘録】Pythonにおけるアンダースコア"_"の役割について - Qiita
- 作者: Bill Lubanovic,斎藤康毅,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/12/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る