《余録》継承の隘路 - 継承は必要悪か〈Python 3.0 版〉

実録:はじめてのプログラミング記事一覧
《余録》 継承は必要悪か __getattribute__

《著》小粒ちゃん+α《監修》小泉ひよ子とタマゴ倶楽部
第3版♪2009/02/28

■ 概要

特殊メソッド __getattribute__ を利用すると、「継承の問題点」を回避する、新たな方策が可能になります。

 OOP の黎明期には脚光を浴びた継承も、やがて、さまざまな問題点が指摘されることになります。
 その反省から、継承から委譲へとパラダイムシフトが起こりますが、では継承は「必要悪」なのでしょうか。
 継承は「必要悪」委譲は「救世主」とするステレオタイプには「再」再考の余地があります。

 《Note》Python2.2.x/Python2.5.x 用に作成したセミナー課題を、Python3.0 で再構成しました。
■ 関連記事
参考文献 note
JavaプログラマのためのUML活用ガイド―例題に学ぶオブジェクト指向プログラミング設計

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

》作業中です《

Last updated♪09/06/02