Python3の復習(プロパティ)
こんにちは。
かにたまごです。
とりあえず統計学の本読み終わりましたが、改めて数学の重要さを知りました・・・。
何事も基礎ができていないと応用はできませんよね・・・。
今はとりあえず頭をリフレッシュさせるために記事を書いたり、スクレイピングしたりしてます。
そこら辺の記事も今後書いていこうかな。
さて、今回は
- プロパティ
について、復習していきたいと思います。
今までのPython3 復習履歴は以下の通り。
- 数値・文字列
- リスト・タプル
- 辞書・集合
- if文・while文・for文
- 内包表記
- 関数
- ジェネレータ
- デコレータ
- 名前空間とスコープ
- アンダーバー・アンダースコアの意味
- エラー処理:try・except
- モジュール・パッケージ
- 標準ライブラリ
- オブジェクトとクラス①-クラスの定義-
- オブジェクトとクラス②-継承-
プロパティ
- ゲッター・セッター
非公開属性の値を読み書きできるようにするのがゲッターとセッター。
どちらか一方に限定すれば、書き込み専用・読み込み専用という属性を作ることができる。
読み込み専用はレンタルDVDとか?でも書き込み専用ってどういうことだろう・・・。
まぁそれは置いておいて、とりあえずコード。
class TEST(): def __init__(self, name): self.hidden_name = name def getter(self): print("hidden_nameの値を返す") return self.hidden_name def setter(self, new_name): print("hidden_nameの値を上書きする") self.hidden_name = new_name #ゲッターで値を取得 test = TEST("かにたまご") print(test.getter()) #セッターで値を上書き test.setter("かにたまごん") #ゲッターで値を取得 print(test.getter())
hidden_nameの値を返す かにたまご hidden_nameの値を上書きする hidden_nameの値を返す かにたまごん
これが属性に直接アクセスするのではなく、ゲッターとセッターを介して値の取得・変更をする方法。
でもこれって
普通に self.name で読み込み・書き込みする場合と何が違うの・・・?
うーん。考えてみたけど、セッター内部で条件分岐するくらいしか思いつかなかった。
class TEST(): def __init__(self, name): self.hidden_name = name self.change_count = 0 def getter(self): print("hidden_nameの値を返す") return self.hidden_name def setter(self, new_name): self.change_count += 1 if self.change_count <= 3: print(self.change_count, "回目の値変更") self.hidden_name = new_name else: print("これ以上は値を変更できません") test = TEST("かにたまご") print(test.getter()) #一回目の変更 test.setter("かにたまごん") print(test.getter()) #二回目の変更 test.setter("かにかま") print(test.getter()) #三回目の変更 test.setter("かにえびたらこ") print(test.getter()) #四回目の変更 test.setter("たまごまん") print(test.getter())
hidden_nameの値を返す かにたまご 1 回目の値変更 hidden_nameの値を返す かにたまごん 2 回目の値変更 hidden_nameの値を返す かにかま 3 回目の値変更 hidden_nameの値を返す かにえびたらこ これ以上は値を変更できません hidden_nameの値を返す かにえびたらこ
・・・何この仕様。
さらにさらに、3回しか変更を受け付けない上に、「かにたまご」のワードが入っていないと弾かれる鬼のセッター。
class TEST(): def __init__(self, name): self.hidden_name = name self.change_count = 0 def getter(self): print("hidden_nameの値を返す") return self.hidden_name def setter(self, new_name): self.change_count += 1 if "かにたまご" in new_name and self.change_count <= 3: print(self.change_count, "回目の値変更") self.hidden_name = new_name else: self.change_count -= 1 print("値を変更できません") test = TEST("かにたまご") print(test.getter()) #一回目の変更 test.setter("かにたまごん") print(test.getter()) #二回目の変更 test.setter("かにたまごまん") print(test.getter()) #三回目の変更 test.setter("かにえびたらこ") print(test.getter()) #四回目の変更 test.setter("あいむかにたまご") print(test.getter()) #五回目の変更 test.setter("あいあむかにたまご") print(test.getter())
hidden_nameの値を返す かにたまご 1 回目の値変更 hidden_nameの値を返す かにたまごん 2 回目の値変更 hidden_nameの値を返す かにたまごまん 値を変更できません hidden_nameの値を返す かにたまごまん 3 回目の値変更 hidden_nameの値を返す あいむかにたまご 値を変更できません hidden_nameの値を返す あいむかにたまご
- プロパティ
確かに、ゲッターやセッターを使うと不用意な書き換えなどを防ぐことができる。
しかし、毎回 getter()やsetter()などで呼び出すのは億劫だし、何より「使う側」の視点に立った時の利便性が悪い。
そこで、あたかも属性にアクセスしているかの如く読み込みと書き込みができる機能がプロパティ。
先ほどのコードにproperty(getter, setter) を付け足すだけで、属性のようにアクセスすることができる。
この場合、property(ゲッター, セッター) という順にする必要がある。
class TEST(): def __init__(self, name): self.hidden_name = name self.change_count = 0 def getter(self): print("hidden_nameの値を返す") return self.hidden_name def setter(self, new_name): self.change_count += 1 if "かにたまご" in new_name and self.change_count <= 3: print(self.change_count, "回目の値変更") self.hidden_name = new_name else: self.change_count -= 1 print("値を変更できません") #これを付け足す name = property(getter, setter)
test = TEST("かにたまご") print(test.name) test.name = "かにたまごん" print(test.name) test.name = "かにたまごまん" print(test.name) test.name = "かにえびたらこ" print(test.name) test.name = "あいむかにたまご" print(test.name) test.name = "あいあむかにたまご" print(test.name)
hidden_nameの値を返す かにたまご 1 回目の値変更 hidden_nameの値を返す かにたまごん 2 回目の値変更 hidden_nameの値を返す かにたまごまん 値を変更できません hidden_nameの値を返す かにたまごまん 3 回目の値変更 hidden_nameの値を返す あいむかにたまご 値を変更できません hidden_nameの値を返す あいむかにたまご
プロパティを定義する際に順番を気にするのは面倒・・・。
そんな時には、デコレータでプロパティを定義することもできる。
ゲッターの前に @property を付け、セッターの前に ゲッター名.setter を付けることで、プロパティを定義できる。
class TEST(): def __init__(self, name): self.hidden_name = name self.change_count = 0 @property def user_name(self): print("hidden_nameの値を返す") return self.hidden_name @user_name.setter def user_name(self, new_name): self.change_count += 1 if "かにたまご" in new_name and self.change_count <= 3: print(self.change_count, "回目の値変更") self.hidden_name = new_name else: self.change_count -= 1 print("値を変更できません") test = TEST("かにたまご") print(test.user_name) test.user_name = "かにたまごん" print(test.user_name) test.user_name = "かにたまごまん" print(test.user_name) test.user_name = "かにえびたらこ" print(test.user_name) test.user_name = "あいむかにたまご" print(test.user_name) test.user_name = "あいあむかにたまご" print(test.user_name)
hidden_nameの値を返す かにたまご 1 回目の値変更 hidden_nameの値を返す かにたまごん 2 回目の値変更 hidden_nameの値を返す かにたまごまん 値を変更できません hidden_nameの値を返す かにたまごまん 3 回目の値変更 hidden_nameの値を返す あいむかにたまご 値を変更できません hidden_nameの値を返す あいむかにたまご
ゲッター名とセッター名は合わせなくてももちろん良い。
プロパティの意味が・・・って感じだけど。
class TEST(): def __init__(self, name): self.hidden_name = name self.change_count = 0 @property def get_user_name(self): print("hidden_nameの値を返す") return self.hidden_name @get_user_name.setter def set_user_name(self, new_name): self.change_count += 1 if "かにたまご" in new_name and self.change_count <= 3: print(self.change_count, "回目の値変更") self.hidden_name = new_name else: self.change_count -= 1 print("値を変更できません") test = TEST("かにたまご") print(test.get_user_name) test.set_user_name = "かにたまごん" print(test.get_user_name) test.set_user_name = "かにたまごまん" print(test.get_user_name) test.set_user_name = "かにえびたらこ" print(test.get_user_name) test.set_user_name = "あいむかにたまご" print(test.get_user_name) test.set_user_name = "あいあむかにたまご" print(test.get_user_name)
hidden_nameの値を返す かにたまご 1 回目の値変更 hidden_nameの値を返す かにたまごん 2 回目の値変更 hidden_nameの値を返す かにたまごまん 値を変更できません hidden_nameの値を返す かにたまごまん 3 回目の値変更 hidden_nameの値を返す あいむかにたまご 値を変更できません hidden_nameの値を返す あいむかにたまご
- 名前のマングリング
先ほどの例では、 hidden_name という属性名を用いて、直接書き換えられないように工夫していた。
しかし
test = TEST("かにたまご") print(test.hidden_name)
かにたまご
このようにアクセスすれば、属性にアクセスできてしまう。
勘の良い人のためには、もう少し工夫する必要がある。
ここで登場するのが、マングリングと呼ばれる属性非公開の手法。
非公開にしたい属性名の前に __(アンダーバーを二つ) つけてあげると、マングリングされる。
class MyFavoriteAnime(): def __init__(self, genre): self.__genre = genre self.__names = [] @property def favorite_anime(self): return self.__names @favorite_anime.setter def favorite_anime(self, name): self.__names.append(name) @property def genre(self): return self.__genre
ここで、 __genre にアクセスしてみる。
gag = MyFavoriteAnime("ギャグ") print(gag.__genre)
Traceback (most recent call last): File "blog_test.py", line 1036, in <module> print(gag.__genre) AttributeError: 'MyFavoriteAnime' object has no attribute '__genre'
マングリングされた属性は、そう簡単にはアクセスできない(`・ω・´)
無理やりアクセスしようとすると、このような記述になる。
gag = MyFavoriteAnime("ギャグ") print(gag._MyFavoriteAnime__genre)
ギャグ
こんな面倒臭い記述をするより、素直に genre プロパティを通じて値を取得するだろう。
ちなみに、この genre は読み込み専用なので、書き込みをする場合は上記の面倒臭いコードを書かなければいけない。
gag = MyFavoriteAnime("ギャグ") print(gag.genre) gag.genre = "日常"
ギャグ Traceback (most recent call last): File "blog_test.py", line 1038, in <module> gag.genre = "日常" AttributeError: can't set attribute
gag = MyFavoriteAnime("ギャグ") print(gag.genre) #gag.genre = "日常" gag._MyFavoriteAnime__genre = "日常" print(gag.genre)
ギャグ 日常
リストの方もマングリングされている。
gag = MyFavoriteAnime("ギャグ") gag.__names.append(1)
File "blog_test.py", line 1036, in <module> gag.__names.append(1) AttributeError: 'MyFavoriteAnime' object has no attribute '__names'
gag = MyFavoriteAnime("ギャグ") gag.favorite_anime = "斉木楠雄のΨ難" gag.favorite_anime = "ヒナまつり" gag.favorite_anime = "ガヴリールドロップアウト" print(gag.favorite_anime)
['斉木楠雄のΨ難', 'ヒナまつり', 'ガヴリールドロップアウト']
なんかモヤモヤして上手くまとめられない。
大規模開発の場合はもちろんだけど、個人で開発するアプリだったりも積極的にプロパティは使った方が良いのかな。
以上です。