Python: クラス名とプロパティ名を同じにしたいときの回避案
同一名にすると期待する参照ができない。名前の重複を回避するしかない。
問題
同一名前空間にクラス名とプロパティ名に同じ名前を使いたい。しかし同一名にしてしまうと、プロパティ値を取得したいのに、プロパティ定義の参照ポインタが取得されてしまう。
予備知識
期待 expect
プロパティ実行値が取得される。
結果 actual
プロパティ定義の参照ポインタが取得される。
コード例
class Parent(object): def __init__(self): self.__child = Parent.Child() @property def Child(self): return self.__child class Child(object): @property def String(self): return 'Child.String value.'
Child
という名前が重複している。クラス名とプロパティ名が、同じParent
名前空間の配下にあるためParent.Child
という参照では以下のどちらであるか一意に特定できないはず。
- Parent.Childプロパティのオブジェクト
- Parent.Childクラスのオブジェクト
以下のようにParent
インスタンスからChild
を参照すると、プロパティのオブジェクトが取得される。
if __name__ == '__main__': p = Parent() print(p.Child.String) # class名とproperty名が重複しているせいでclass定義のほうを参照してしまう? # 期待値: 'Child.String value.' # 実際値: <property object at 0xb70e8c0c> プロパティ定義のポインタ?
0xb70e8c0c
はChildプロパティのオブジェクトにおける先頭アドレスと思われる。
妥協案
インナークラス名の先頭に__
を付与して名前重複を避ける。
ただしアクセス修飾子がPrivateになるためふつうの方法では外部から参照できなくなる。
class Parent(object): def __init__(self): # self.__child = Parent.Child() self.__child = Parent.__Child() @property def Child(self): return self.__child # class Child(object): class __Child(object): @property def String(self): return 'Child.String value.' if __name__ == '__main__': c = Parent() print(c.Child.String) # 期待通り! 'Child.String value.'
Privateでも参照できてしまう仕様
インナークラスをPrivateにすると外部から参照できなくなる?参照できる。ただし以下のようなコードになる。
mimetype = Parent()._Parent__Child()
print(mimetype.String)
mimetype = ContentType()._ContentType__MimeType()
print(mimetype.String)
以下の書式でPrivateメンバ要素を取得できる。
{parent_instance}._{parent_type}__{inner_private_type}
単体テストするときもこの参照方法でインナークラスのインスタンスを生成すればいい。これはPythonの言語仕様である。Pythonではカプセル化できない。
使いどころ
インナークラスを使う意義
包含関係を表せる。とくにコードに書いた時のインデント階層がそのままクラスの包含関係を表すことができる。
インナークラスのアクセス修飾子
今回の場合は以下の条件下で有用。
要素 | アクセス修飾子 |
---|---|
インナークラス定義 | Private |
インナークラスオブジェクト | Private |
インナークラスインスタンス | Public |
インナークラスやインナークラスオブジェクトはPublicである必要はない。インナークラスインスタンスだけ外部から参照できれば良い。なぜならインナークラスのインスタンス生成は親クラスが定めた一定の処理で行うから。外部からインナークラスが見えてしまうと好き勝手にインスタンスを生成できてしまう。そのような用途は想定していない。
複数ネストしたインナークラスの参照は?
親クラスのインスタンスからPrivateメンバ要素参照すればいい。
class Response(object): class __Headers(object): class __ContentType(object): class __MimeType(object): class __SubType(object): pass if __name__ == '__main__': r = Response() h = r._Response__Headers() c = h._Headers__ContentType() m = c._ContentType__MimeType() s = m._MimeType__SubType()
複数ネストしたインナークラスの参照とそのプロパティを用意するには?
- インナークラス名の先頭に
__
を付与して名前重複回避&Private化 {parent_instance}._{Parent_type}__{Inner_private_type}
によりPrivateインナークラス型を参照する{parent_instance}.{Child_reference_property}
によりプロパティ呼出する
class Response(object): def __init__(self): self.__headers = self._Response__Headers() @property def Headers(self): return self.__headers class __Headers(object): def __init__(self): self.__content_type = self._Headers__ContentType() @property def ContentType(self): return self.__content_type class __ContentType(object): def __init__(self): self.__mime_type = self._ContentType__MimeType() @property def MimeType(self): return self.__mime_type class __MimeType(object): def __init__(self): self.__sub_type = self._MimeType__SubType() @property def SubType(self): return self.__sub_type class __SubType(object): pass if __name__ == '__main__': # クラスのインスタンス生成 r = Response() h = r._Response__Headers() c = h._Headers__ContentType() m = c._ContentType__MimeType() s = m._MimeType__SubType() # プロパティ実行して子インナークラスのインスタンスを取得する print(r) print(r.Headers) print(h.ContentType) print(c.MimeType) print(m.SubType) # プロパティ実行結果。インスタンスのクラス名と先頭アドレスに見える # <__main__.Response.__Headers object at 0x00000001> # <__main__.Response.__Headers.__ContentType object at 0x00000002> # <__main__.Response.__Headers.__ContentType.__MimeType object at 0x00000003> # <__main__.Response.__Headers.__ContentType.__MimeType.__SubType object at 0x00000004>