《余録》継承の隘路 - 継承は必要悪か〈Python 3.0 版〉
実録:はじめてのプログラミング《記事一覧》
《余録》 継承は必要悪か __getattribute__
■ 概要
特殊メソッド __getattribute__ を利用すると、「継承の問題点」を回避する、新たな方策が可能になります。
OOP の黎明期には脚光を浴びた継承も、やがて、さまざまな問題点が指摘されることになります。 その反省から、継承から委譲へとパラダイムシフトが起こりますが、では継承は「必要悪」なのでしょうか。 継承は「必要悪」委譲は「救世主」とするステレオタイプには「再」再考の余地があります。 《Note》Python2.2.x/Python2.5.x 用に作成したセミナー課題を、Python3.0 で再構成しました。
■ 関連記事
参考文献 | note |
---|---|
JavaプログラマのためのUML活用ガイド―例題に学ぶオブジェクト指向プログラミング設計
|
「継承」の問題を回避するために「委譲」を活用する方法を紹介しています。 |
事例:スタックを実現する
組み込み型 list の動作を「継承」によって、自由に拡張/抑制できます。そこで、list の機能を再利用するとともに、スタックを実現するのに必要のない機能はすべて無効にします。
■ 機能の抑制
特殊なメソッド __getattribute__ は、任意の属性へのアクセスを横取りして、ドット「.」による値の参照/変更、メソッド呼び出しなどを自由に制御できます。
class Stack(list): def __getattribute__(self, name): dirs = { "push": "append", "pop" : "pop" , } name = dirs.get(name) if not name: raise AttributeError return list.__getattribute__(self, name)
クラス Stack は、組み込み型 list を継承したにも関わらず、メソッド push/pop 以外をすべて無効にします。メソッド push は、既存のメソッド append を再利用します。また、メソッド pop は、既存のメソッド pop の動作を、次のように変更します。
■ 機能の拡張(変更)
継承によって、既存のメソッドの動作を自由に拡張できます。
def pop(self): try: return list.pop(self) except: return None
メソッド pop は(組み込み型 list とは違って)空のリストに対して例外を生成せずに、None をリターン値にします。
事例:スタックを利用する
>>> s = Stack("AB")
>>> s
['A', 'B']
クラス Stack は、組み込み型 list を拡張(継承により実現)したものです。
■ 既存の動作を無効にする
>>> s.append("C") Traceback (most recent call last): ... raise AttributeError AttributeError >>> s ['A', 'B']
メソッド append は、組み込み型 list には有効でも、それを拡張したクラス Stack には無効です。
■ 既存の動作を横取りする
>>> s.push("C")
>>> s
['A', 'B', 'C']
メソッド push は(組み込み型 list に有効な)メソッド append と同じ動作をします。
■ 既存の動作を変更する
>>> s = list() >>> s [] >>> s.pop() Traceback (most recent call last): File "", line 1, in IndexError: pop from empty list
(組み込み型 list では)空リストにメソッド pop を適用すると、例外 IndexError を生成して、エラーメッセージを出力します。
>>> s = Stack("AB") >>> s ['A', 'B'] >>> s.pop() 'B' >>> s.pop() 'A' >>> s.pop() >>> s []
(組み込み型 list と違って)空リストにメソッド pop を適用しても、例外を生成せずに、単に None が得られます。ただし、対話モードでは何も(None を)出力しません。
■ 既存の動作を維持する
>>> len(s) 3 >>> "A" in s True >>> [ord(e) for e in s] [65, 66, 67] >>> {e:ord(e) for e in s} # Python3.0 {'A': 65, 'C': 67, 'B': 66} >>> dict((e,ord(e)) for e in s) {'A': 65, 'C': 67, 'B': 66}
(組み込み型 list と同様に)Stack にも、組み込み関数、演算子だけでなく、リスト/辞書の内包、ジェネレーター式なども有効です。
Tips
》作業中です《