はじめに
この記事のコードは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がいるのに?と思いましたが。
理由がわかる方がいたらぜひコメントで教えてください。