Pythonで「実装するべき箇所」をコードであらわす
例えば、実装最中に「現時点では、この詳細な仕様は決められないけれども、しかし定義だけはしておく必要がある」というパターンがあると思います。
で、普通はそういった場合、下のように書かれることが常ではないかなと思います。
# TODO: foobarを実装する
要するに、コメントに書いて、あとでgrepであったり、Jenkinsに回収させるといった方法ですね(Jenkinsには、Task Scan Pluginというものがあります)。
確かに、コメントに書いておくのも一つの方法なのですが、自分が印象深かったのは、Scala (or Java) のWebフレームワークであるPlayのソースコードです。参考までに、チュートリアルから引っ張ってきます。
object Application extends Controller { def index = Action { Ok("Hello world") } def tasks = TODO def newTask = TODO def deleteTask(id: Long) = TODO }
つまり、まだ定義されていないアクションに関しては、TODOを渡すことによって、TODO用の画面が表示されるようになる。これを始めてみたときには「へー!面白い」と思いました。
何が面白かったのかといえば、TODOであること自体をコードとして埋め込むという発想は、自分の中ではなかったからです。なので、これをみたあとは、djangoとかでも、テンプレートとして「TODO.html」を定義して、それを使っています。
さて、上の場合は、MVCのWebフレームワークにおけるViewの問題になるわけですが、ではそれ以外の、クラスの場合だったらどうでしょうか。
なるほど、PythonにはJavaのようなインターフェイスを実装する方法としてabcというライブラリが登場しています(2.6以降)。
import abc class PluginBase(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def load(self, input): """Retrieve data from the input source and return an object.""" return @abc.abstractmethod def save(self, output, data): """Save the data object to the output.""" return
詳しくは、上記のリンクのドキュメントを読んでいただければわかるのですが、デコレーターで定義されたメソッドを実装せずにインスタンスを作ろうとすると、エラーが起きるように仕向けることができます。
しかし、ドキュメントを見てみればわかるとおり、このライブラリを使って抽象化した場合、abstractmethodで指定されたメソッドを「全て」実装しないといけないことになります。こうすると困る例があります。
どういうことかといえば、なんらかのクラスを継承した場合でも、「使うかも知れないが、使うとなると実装しないと困るメソッド」があります。逆に考えれば、「使わない限り、そのメソッドをオーバーライドする必要がない」というときには、abcを使うのは不便のような気がします。
そこで、実際にdjangoのソースを見てみましょう。例えば、formのwidgetのベースとなっているクラスは、次のように宣言されています。
class Widget(six.with_metaclass(MediaDefiningClass)): is_hidden = False # Determines whether this corresponds to an <input type="hidden">. needs_multipart_form = False # Determines does this widget need multipart form is_localized = False is_required = False def __init__(self, attrs=None): if attrs is not None: self.attrs = attrs.copy() else: self.attrs = {} (...中略...) def render(self, name, value, attrs=None): """ Returns this Widget rendered as HTML, as a Unicode string. The 'value' given is not guaranteed to be valid input, so subclass implementations should program defensively. """ raise NotImplementedError
ここで気がつくことがひとつあります。それは、このメソッドはabcライブラリを使わずに、あえて例外を吐き出すように実装されています。
これはどういうことか。Pythonには、NotImplementedErrorという組み込み関数があります。この組み込み例外の目的は、ドキュメントから引用すると次のような形になるでしょう。
この例外は RuntimeError から派生しています。ユーザ定義の基底クラスにおいて、抽象メソッドが派生クラスでオーバライドされることを要求する場合、この例外を送出しなくてはなりません。
つまり、「俺を呼び出すんだったらオーバーライドしろやコラ」という話ですね。ただし、これはあくまでも"raise"で呼び出すものなので、そのメソッドが呼び出されない限りは、放置していても大丈夫、という風に自分は理解しています。
恐らく、abstructmethodとエラーを吐き出すことの違いは、それが「呼び出されることが決定されているか」、あるいは「現時点ではどうなるかわからない曖昧なメソッド」という部分になるのかな、と推測しています。