やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

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>