インスタンスレベルでメソッドをオーバーライドする


87

Pythonにインスタンスレベルでクラスメソッドをオーバーライドする方法はありますか?例えば:

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!

回答:


11

図のようにこれを行わないでください。インスタンスをクラスとは異なるものにモンキーパッチすると、コードが読み取れなくなります。

モンキーパッチされたコードをデバッグすることはできません。

bobyprint type(boby)でバグを見つけると、(a)犬ですが、(b)なんらかの理由で、正しく吠えないことがわかります。これは悪夢です。それをしません。

代わりにこれを行ってください。

class Dog:
    def bark(self):
        print "WOOF"

class BobyDog( Dog ):
    def bark( self ):
        print "WoOoOoF!!"

otherDog= Dog()
otherDog.bark() # WOOF

boby = BobyDog()
boby.bark() # WoOoOoF!!

9
@arivero:「示されているようにこれをしないでください」がそれを完全に明確にしたと思いました。これが尋ねられた質問に答えていないが、なぜそれが悪い考えであるかについてのアドバイスを提供していることをより明確にするために、他にどのようなまたは異なる言葉を見たいですか?
S.Lott 2012年

45
私はアドバイスにも、見た目どおりのOPにも同意しません。しかし、私は人々が尋ねる理由があると思います。または、OPがそうでなかったとしても、他の将来の訪問者はそうすることができます。ですから、私見、答えと叱責は単なる叱責よりも優れています。
arivero 2012年

13
@arivero:それは私の質問に答えませんでした。
S.Lott 2012年

9
@ S.Lott「表示された」を実際の回答にリンクする必要があると思います。これが受け入れられた回答であることに問題はありませんが、状況によってはモンキーパッチが必要な理由があります。私は「示されているように」を、別の答えではなく、あなたが示しているものを意味すると解釈しました。
ダニエルチャットフィールド2013

4
サブクラス化は、メソッドにモンキーパッチを適用するよりもはるかに強力なコントラクトです。可能であれば、強力な契約は予期しない行動を防ぎます。しかし、場合によっては、より緩い契約が望ましいこともあります。そのような状況では、モンキーパッチの代わりにコールバックを使用することをお勧めします。これは、一部の動作をカスタマイズできるようにすることで、クラスコントラクトが違反されないままになるためです。あなたの答えは、実用的なアドバイスではありますが、この質問に対してはかなり貧弱であり、コーディングスタイルに一貫性がありません。
Aaron3468 2016

166

はい、それは可能です:

class Dog:
    def bark(self):
        print "Woof"

def new_bark(self):
    print "Woof Woof"

foo = Dog()

funcType = type(Dog.bark)

# "Woof"
foo.bark()

# replace bark with new_bark for this object only
foo.bark = funcType(new_bark, foo, Dog)

foo.bark()
# "Woof Woof"

1
funcType(new_bark、foo、Dog)を実行すると、名前== barkとインスタンスメソッドDog.new_barkがfoo .__ dict__に追加されます。だから、あなたはそのためのインスタンス辞書とコールへの最初のルックアップ再びそれを呼び出すときに、
ジェームズ

11
これが何をするのか、特に何をするのかfuncType、なぜそれが必要なのかを説明してください。
Aleksandr Dubinsky 2016

3
の代わりにfuncType = types.MethodType(インポート後types)を使用することで、これを少し簡単かつ明確にできると思いますfuncType = type(Dog.bark)
エリアスザマリア2016年

13
これはPython3では機能しないと思います。「TypeError:function()引数1は関数ではなくコードである必要があります」というエラーが発生します。Python 3に関する提案はありますか?
2016

2
__get__関数を呼び出して、インスタンスにバインドすることもできます。
狂牛病の物理学者

37

モジュールからMethodTypeを使用する必要がありtypesます。の目的MethodTypeは、インスタンスレベルのメソッドを上書きselfすることです(上書きされたメソッドで使用できるようにするため)。

以下の例を参照してください。

import types

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print "WoOoOoF!!"

boby.bark = types.MethodType(_bark, boby)

boby.bark() # WoOoOoF!!

31

@codelogicの優れた答えを説明するために、より明確なアプローチを提案します。これは、.メソッドが実際にはクラスの外部で定義された関数になることを除いて、インスタンス属性としてアクセスするときにオペレーターがクラスメソッドをバインドするために徹底的に行うのと同じ手法です。

@codelogicのコードを使用する場合、唯一の違いはメソッドのバインド方法にあります。関数とメソッドがPythonの非データ記述子であるという事実を利用して、__get__メソッドを呼び出しています。特に、元の署名と置換の両方が同一のシグネチャを持っていることに注意してください。つまり、を介してすべてのインスタンス属性にアクセスし、完全なクラスメソッドとして置換を記述できますself

クラス犬:
    def bark(self):
        「Woof」を印刷

def new_bark(self):
    「WoofWoof」を印刷

foo = Dog()

#「ウーフ」
foo.bark()

#このオブジェクトの場合のみ、樹皮をnew_barkに置き換えます
foo.bark = new_bark .__ get __(foo、Dog)

foo.bark()
#「WoofWoof」

バインドされたメソッドをインスタンス属性に割り当てることにより、メソッドのオーバーライドのほぼ完全なシミュレーションを作成しました。欠落している便利な機能の1つsuperは、クラス定義に含まれていないため、引数のないバージョンへのアクセスです。もう1つのことは__name__、バインドされたメソッドの属性は、クラス定義の場合のように、オーバーライドする関数の名前を取りませんが、手動で設定することはできます。3番目の違いは、手動でバインドされたメソッドは、たまたま関数であるプレーンな属性参照であるということです。.オペレータは何もしませんが、その参照を取得します。一方、インスタンスから通常のメソッドを呼び出す場合、バインディングプロセスは毎回新しいバインドされたメソッドを作成します。

ちなみに、これが機能する唯一の理由は、インスタンス属性が非データ記述子をオーバーライドすることです。データ記述子には__set__メソッドがありますが、(幸いなことに)メソッドにはありません。クラス内のデータ記述子は、実際にはどのインスタンス属性よりも優先されます。これが、プロパティに割り当てることができる理由です__set__。割り当てを行おうとすると、そのメソッドが呼び出されます。私は個人的にこれをさらに一歩進めて、インスタンスの基になる属性の実際の値を非表示にするのが好きです。インスタンス__dict__は、プロパティがシャドウしているため、通常の方法ではアクセスできません。

また、これは魔法の(二重下線)メソッドには無意味であることも覚えておく必要があります。もちろん、マジックメソッドはこの方法でオーバーライドできますが、マジックメソッドを使用する操作はタイプのみを調べます。たとえば__contains__、インスタンスで特別なものに設定できますが、呼び出しx in instanceはそれを無視してtype(instance).__contains__(instance, x)代わりに使用します。これは、Pythonデータモデルで指定されているすべてのマジックメソッドに適用されます。


1
それはきれいだとPython 3で動作します。これは受け入れ答えなければなりません
BlenderBender

@BlenderBender。私はあなたのサポートに感謝します
マッド物理学者

この回答と@HarshalDhumaiの上記の回答の違いは何ですか?
1313E

1
@ 1313。機能的には、大きな違いはないはずです。上記の答えは、入力としてより広い範囲の呼び出し可能オブジェクトを受け入れる可能性があると思いますが、ドキュメントを読んで遊んでいなければ、わかりません。
狂牛病の物理学者

このためのPython2ドキュメント:docs.python.org/2/howto/descriptor.html#functions-and-methods。つまり、理論的には@HarshalDhumaiの答えと同じです。
Rockallite

26
class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

# METHOD OVERRIDE
def new_bark():
    print "WoOoOoF!!"
boby.bark = new_bark

boby.bark() # WoOoOoF!!

boby必要に応じて、関数内で変数を使用できます。この1つのインスタンスオブジェクトに対してのみメソッドをオーバーライドするため、この方法はより簡単で、を使用する場合とまったく同じ効果がありselfます。


1
元の署名を使用するIMHOは、特に関数がインスタンスの近くではなくコードの他の場所で定義されている場合に、読みやすさを向上させます。例外は、オーバーライドメソッドも関数として独立して使用される場合です。もちろん、この単純な例では、それは問題ではありません。
codelogic 2008

1
これが受け入れられない答えである理由がわかりません。これは呼び出されpatching、これが正しい方法です(例:boby = Dog()およびboby.bark = new_bark)。これは、コントロールの単体テストで非常に役立ちます。詳細については、tryolabs.com / blog / 2013/07/05 / run - time - method - patching - python(例)を参照してください-いいえ、リンクされたサイトまたは作成者とは提携していません。
ジェフ

5
new_barkメソッドにはself(インスタンス)へのアクセス権がないため、ユーザーがnew_barkのインスタンスプロパティにアクセスする方法はありません。代わりに、typesモジュールのMethodTypeを使用する必要があります(以下の私の回答を参照)。
Harshal Dhumal 2017

3

functools.partialここでは誰も言及していないので:

from functools import partial

class Dog:
    name = "aaa"
    def bark(self):
        print("WOOF")

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print("WoOoOoF!!")

boby.bark = partial(_bark, boby)
boby.bark() # WoOoOoF!!

1

関数はPythonのファーストクラスオブジェクトであるため、クラスオブジェクトの初期化中にそれらを渡すか、特定のクラスインスタンスに対していつでもオーバーライドできます。

class Dog:
    def __init__(self,  barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        print "woof"

d=Dog()
print "calling original bark"
d.bark()

def barknew():
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()

def barknew1():
    print "nowoof"

d1.bark=barknew1
print "calling another new"
d1.bark()

結果は

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof

-4

S. Lottからの継承のアイデアが好きで、「type(a)」のことには同意しますが、関数にもアクセス可能な属性があるため、次のように管理できると思います。

class Dog:
    def __init__(self, barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        """original bark"""
        print "woof"

d=Dog()
print "calling original bark"
d.bark()
print "that was %s\n" % d.bark.__doc__

def barknew():
    """a new type of bark"""
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

def barknew1():
    """another type of new bark"""
    print "nowoof"

d1.bark=barknew1
print "another new"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

出力は次のとおりです。

calling original bark
woof
that was original bark

calling the new bark
wooOOOoof
that was a new type of bark

another new
nowoof
that was another type of new bark

2
それを「管理」する必要がある場合は、私にとっては何かがおかしいのです。特に、すでに仕事をしている一流の言語機能がある場合。
S.Lott 2008

-4

親愛なるこれは、オブジェクトで同じ関数を2回呼び出すだけでオーバーライドされません。基本的に、オーバーライドは複数のクラスに関連しています。同じ署名メソッドが異なるクラスに存在する場合、これを呼び出す関数によって、これを呼び出すオブジェクトが決まります。複数のクラスを作成して同じ関数を書き込むと、Pythonでオーバーライドが可能になります。また、Pythonではオーバーロードが許可されていないことを共有する必要があります。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.