2

Python にクラス 'D' のオブジェクトがあり、'D' とその祖先 ('A'、'B'、'C') で定義されている 'run' メソッドを順次実行したいと考えています。

私はこのようにこれを達成することができます

class A(object):
    def run_all(self):
        # I prefer to execute in revere MRO order
        for cls in reversed(self.__class__.__mro__):
            if hasattr(cls, 'run'):
                # This works
                cls.run(self)
                # This doesn't
                #cls.__getattribute__(self, 'run')()

    def run(self):
        print "Running A"

class B(A):
    def run(self):
        print "Running B"

class C(A):
    def run(self):
        print "Running C"

class D(C, B):
    def run(self):
        print "Running D"

if __name__ == "__main__":
    D().run_all()

その結果、

$ python test.py 
Running A
Running B
Running C
Running D

ただし、実際には、実行されるメソッドの名前はわかりません。しかし、getattribute () (コメントを参照) 行を使用してこれを試してみると、機能しません。

$ python test.py 
Running D
Running D
Running D
Running D

だから私の質問は:

  1. なぜ機能しないのですか?

  2. これはこれを行うための最良の方法ですか?

4

3 に答える 3

4

すべての実装を変更しても (そしてDではなくrun呼び出しても) OK であれば、次のように動作します。runrun_all

class A(object):
    def run(self):
        print "Running A"

class B(A):
    def run(self):
        super(B, self).run()
        print "Running B"

class C(A):
    def run(self):
        super(C, self).run()
        print "Running C"

class D(C, B):
    def run(self):
        super(D, self).run()
        print "Running D"

if __name__ == "__main__":
    D().run()

ルート クラスでは使用しないことに注意してくださいsuper。ルート クラスは、それ以上のスーパークラスがないことを「認識」しています (はメソッドobjectを定義しませんrun)。残念ながら、Python 2 では、これは必然的に冗長になります (また、デコレータによる実装にもあまり適していません)。

あなたの目的を正しく理解していれば、チェックは非常に脆弱です。クラスが定義または継承hasattrしている場合、クラスが属性を「持っている」ことがわかります。したがって、オーバーライドしないが で発生する中間クラスがある場合、継承するバージョンがアプローチで 2 回呼び出されます。たとえば、次のことを考慮してください。run__mro__run

class A(object):
    def run_all(self):
        for cls in reversed(self.__class__.__mro__):
            if hasattr(cls, 'run'):
                getattr(cls, 'run')(self)
    def run(self):
        print "Running A"
class B(A): pass
class C(A):
    def run(self):
        print "Running C"
class D(C, B): pass

if __name__ == "__main__":
    D().run_all()

これは印刷します

Running A
Running A
Running C
Running C

runそのバージョンとオーバーライドせずBD継承するための2つの「スタッター」があります(それぞれからAとからC)。私が正しいと仮定すると、これはあなたが望む効果ではありません。回避したい場合は、次のようにsuper変更run_allしてみてください。

def run_all(self):
    for cls in reversed(self.__class__.__mro__):
        meth = cls.__dict__.get('run')
        if meth is not None: meth(self)

これを最新の例に置き換えて、 in と の 2 つの異なる のみを使用するdefと、例が出力されます。runAC

Running A
Running C

あなたが望むものに近いかもしれないと思います。

もう 1 つの側面: 作業を繰り返さないでください -- getattr を保護する hasattr、またはindict アクセスを保護するテスト -- ガード内のチェックと保護されたアクセサーの両方が、内部でまったく同じ作業を繰り返さなければなりません。むしろ、None単一のgetattr呼び出し (またはgetdict のメソッド) に の 3 番目の引数を使用します。これは、メソッドが存在しない場合に値を取得し、呼び出しをその発生からNone保護できることを意味します。これがまさに、dicts にメソッドがあり、3 番目のオプションの「デフォルト」引数がある理由です。DRY を簡単に適用できるようにするために、「同じことを繰り返さないでください」、優れたプログラミングの非常に重要な格言です!-)getgetattr

于 2010-01-01T23:03:15.190 に答える
1

単純に使ってみませんsuperか?有害だと思われる方もいらっしゃいますが、まさにこのようなシナリオを念頭に置いて設計されており、迷わず使ってみたいと思います。

Pythonドキュメントから:

これは、クラスでオーバーライドされた継承されたメソッドにアクセスする場合に役立ちます。検索順序は、型自体がスキップされることを除いて、getattr()で使用される順序と同じです。[...]これにより、複数の基本クラスが同じメソッドを実装する「ダイアモンド図」を実装できます。

更新:あなたの場合、それは次のようになります:

class A(object):

    def run(self):
        print "Running A"

class B(A):
    def run(self):
        super(B, self).run()
        print "Running B"

class C(A):
    def run(self):
        super(C, self).run()
        print "Running C"

class D(C, B):
    def run(self):
        super(D, self).run()
        print "Running D"

if __name__ == "__main__":
    D().run()
于 2010-01-01T22:38:06.020 に答える
1

__getattribute__メソッドを使用しないでください。

次のことを行うだけです:

getattr(cls, 'run')(self)
于 2010-01-01T22:29:15.813 に答える