Python
インナークラス
バウンドインナークラス

Python バウンドインナークラスを使ってみる

はじめに

この記事のコードはPython3.6上で検証しています。

動機

Railsを使っていると、こういうコードを書くことがよくあります。

class User < ApplicationRecord
  class NameInvalidError < StandardError; end
  class AgeInvalidError < StandardError; end

  ...
end

あるクラスに関連してしか使わないエラーを表すクラスを、関連先のクラスの中で定義しておいて

class UsersController < ApplicationController
  rescue_from User::NameInvalidError do
    ...
  end
  ...
end

みたいな。

最近、個人的にPythonのTornadoというフレームワークを使ってWebアプリの開発を始めたのですが、同じような感じに出て来るコンテクストが限定されている例外クラスをその例外を使うクラスのなかで定義することができないか調べていると、バウンドインナークラスという仕組みがあることを知りました。

バウンドインナークラス

class BaseKlass():
    # こんなかんじでバウンドインナークラスを定義
    class InnerKlass():
        def __init__(self, value):
            self._value = value

        def value(self):
            return self._value

    def __init__(self):
        # Pythonらしく、`InnerKlass`だけだと名前解決できない
        self.inner = BaseKlass.InnerKlass('ハロー ハロー')

    def do(self):
        print(self.inner.value())

class InheritedKlass(BaseKlass):
    def __init__(self):
        # 継承したクラスもインナークラスを持っている
        self.inner1 = BaseKlass.InnerKlass('ぼくから世界へ 応答願います')
        self.inner2 = InheritedKlass.InnerKlass('ぼくらのコードは正しくつながっていますか')

    def do(self):
        print(self.inner1.value())
        print(self.inner2.value())

class MoreInheritedKlass(BaseKlass):
    # 継承もできる
    class InnerKlass(BaseKlass.InnerKlass):
        def __init__(self, value):
            self._value = value

        def value(self):
            return self._value + '───────'

    def __init__(self):
        self.inner1 = BaseKlass.InnerKlass('ぼくの世界は正しく回転している模様 ')
        self.inner2 = MoreInheritedKlass.InnerKlass('システムオールグリーン コミュニケーションは不全')

    def do(self):
        print(self.inner1.value())
        print(self.inner2.value())

if __name__ == '__main__':
    b = BaseKlass()
    b.do()

    i = InheritedKlass()
    i.do()

    mi = MoreInheritedKlass()
    mi.do()

この結果は、次のようになります。

ハロー ハロー
ぼくから世界へ 応答願います
ぼくらのコードは正しくつながっていますか
ぼくの世界は正しく回転している模様
システムオールグリーン コミュニケーションは不全───────

最後の場合のみ、バウンドインナークラスのメソッドがオーバーライドされて出力文字列の最後にダッシュがついています。

使い所

Rubyのようにオープンクラスがないので、階層化された名前空間を作るためにカジュアルに使うようには使えないでしょう。

ただ、冒頭にあげたようにあるクラスの中でしか使わないような例外クラスを定義したい場合などには使えるのではないかと思います。

疑問点

class MoreInheritedKlass(BaseKlass):
    class InheritedInnerKlass(BaseKlass.InnerKlass):
        pass

はOKなのですが、

class MoreInheritedKlass(BaseKlass):
    class InheritedInnerKlass(MoreInheritedKlass.InnerKlass):
        pass

だと NameError: name 'MoreInheritedKlass' is not defined と怒られてしまいます。
MoreInheritedKlassはBaseKlassを継承しているので、InnerKlassがいるのに?と思いましたが。

理由がわかる方がいたらぜひコメントで教えてください。