Örnek düzeyinde bir yöntemi geçersiz kılma


87

Python'da örnek düzeyinde bir sınıf yöntemini geçersiz kılmanın bir yolu var mı? Örneğin:

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

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

Yanıtlar:


10

Lütfen bunu gösterildiği gibi yapmayın. Sınıftan farklı olacak bir örnek eklediğinizde kodunuz okunamaz hale gelir.

Monkeypatched kodda hata ayıklayamazsınız.

bobyVe içinde bir hata bulduğunuzdaprint type(boby) , bunun bir köpek, ama kabuğu doğru değil gibi anlaşılmaz bir sebeple (b) (a) göreceksiniz. Bu bir kabus. Bunu yapma.

Lütfen bunun yerine bunu yapın.

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: "Lütfen bunu gösterildiği gibi yapmayın" ifadesinin bunu tamamen netleştirdiğini düşündüm. Bunun sorulan soruyu yanıtlamadığını, ancak bunun neden kötü bir fikir olduğu konusunda tavsiyelerde bulunduğunu daha açık hale getirmek için başka hangi veya farklı sözcükleri görmek istersiniz?
S. Lot

46
Tavsiyeye veya göründüğü gibi OP'ye katılmıyorum. Ama insanların sormak için nedenleri olduğunu varsayıyorum. Veya OP olmasa bile, gelecekteki diğer ziyaretçiler yapabilir. Öyleyse, IMHO, bir cevap artı bir kınama daha iyidir, sadece bir kınama.
arivero

13
@arivero: Bu soruma cevap vermedi.
S.Lott

9
@ S.Lott "Gösterilen" cevabı gerçek cevaba bağlamanız gerektiğini düşünüyorum, bunun kabul edilen cevap olmasıyla ilgili gerçekten bir sorunum yok, ancak bazı durumlarda ve benim kısa okumamdan dolayı neden yama yapmanız gerektiğine dair nedenler var Farklı bir cevap yerine, gösterdiğiniz şeyi ifade etmek için "gösterildiği gibi" aldım.
Daniel Chatfield

4
Alt sınıflandırma, bir yöntemi maymun yamalı yapmaktan çok daha güçlü bir sözleşmedir. Mümkün olduğunda, güçlü sözleşmeler beklenmedik davranışları önler. Ancak bazı durumlarda, daha gevşek bir sözleşme istenebilir. Bu durumlarda, maymun yaması yerine bir geri arama kullanmayı tercih ederim çünkü bazı davranışların özelleştirilmesine izin vererek sınıf sözleşmesi ihlal edilmeden kalır. Cevabınız, pratik tavsiye olsa da, bu soru için oldukça zayıf ve kodlama tarzınız tutarsız.
Aaron3468

169

Evet mümkün:

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
Küçük bir yorum, funcType (new_bark, foo, Dog) yaptığınızda, foo .__ dict__ için name == bark ve örnek yöntemi Dog.new_bark ekleyerek doğru mu? Öyleyse, tekrar aradığınızda, ilk önce örnek sözlüğüne bakın ve onu arayın,
James

11
Lütfen bunun ne yaptığını, özellikle ne funcTypeişe yaradığını ve neden gerekli olduğunu açıklayın .
Aleksandr Dubinsky

3
Bunun yerine ( funcType = types.MethodTypeiçe aktardıktan sonra types) kullanarak biraz daha basit ve daha açık hale getirilebileceğini düşünüyorum funcType = type(Dog.bark).
Elias Zamaria

13
Sanırım bu Python 3 ile çalışmıyor. "TypeError: function () argüman 1 kod olmalı, işlev değil" hatası alıyorum. Python 3 için herhangi bir öneriniz var mı?
Sait

2
Ayrıca __get__, örneğe bağlamak için işlevi çağırabilirsiniz .
Mad Fizikçi

41

Kullanmanız gerekir MethodType gelen typesmodül. Bunun amacı, MethodTypeörnek düzeyindeki yöntemlerin üzerine yazmaktır (böyleceself üzerine yazılan yöntemlerde mevcut olabilir).

Aşağıdaki örneğe bakın.

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!!

32

@ Codelogic'in mükemmel cevabını açıklamak için daha açık bir yaklaşım öneriyorum. Bu, .bir örnek özniteliği olarak eriştiğinizde , operatörün bir sınıf yöntemini bağlamak için kapsamlı bir şekilde gittiği aynı tekniktir , ancak yönteminizin aslında bir sınıfın dışında tanımlanmış bir işlev olması gerekir.

@ Codelogic'in koduyla çalışırken, tek fark yöntemin nasıl bağlandığıdır. Python'da işlevlerin ve yöntemlerin veri tanımlayıcılar olmadığı gerçeğini kullanıyorum ve yöntemi çağırıyorum __get__. Özellikle, hem orijinalin hem de değiştirilenin aynı imzalara sahip olduğuna dikkat edin, yani değiştirmeyi tam sınıf yöntemi olarak yazabilir, tüm örnek özniteliklerine aracılığıyla erişebilirsiniz.self .

sınıf Köpek:
    def bark (kendi):
        "Hav" yazdır

def new_bark (kendi):
    "Hav Hav" yazdır

foo = Köpek ()

# "Hav"
foo.bark ()

# sadece bu nesne için kabuğu new_bark ile değiştirin
foo.bark = new_bark .__ get __ (foo, Dog)

foo.bark ()
# "Hav! Hav"

Bağlı yöntemi bir örnek niteliğine atayarak, bir yöntemi geçersiz kılmak için neredeyse eksiksiz bir simülasyon yaratmış olursunuz. Eksik olan kullanışlı bir özellik, supersınıf tanımında olmadığınız için argüman içermeyen sürümüne erişimdir . Başka bir şey de, __name__bağlı yönteminizin özniteliğinin, sınıf tanımında olduğu gibi geçersiz kıldığı işlevin adını almayacağıdır, ancak yine de manuel olarak ayarlayabilirsiniz. Üçüncü fark, manuel olarak bağlanmış yönteminizin sadece bir işlev olan düz bir öznitelik referansı olmasıdır. .Operatör hiçbir şey yapmaz ama bu başvuru getir. Öte yandan bir örnekten normal bir yöntemi çağırırken, bağlama işlemi her seferinde yeni bir bağlı yöntem oluşturur.

Bu arada, bunun çalışmasının tek nedeni, örnek niteliklerinin veri olmayan tanımlayıcıları geçersiz kılmasıdır. Veri tanımlayıcıların __set__yöntemleri vardır, ancak bu yöntemlerin (neyse ki sizin için) yoktur. Sınıftaki veri tanımlayıcıları aslında herhangi bir örnek özniteliğine göre önceliklidir. Bir özelliğe atamanın nedeni budur: __set__bir atama yapmaya çalıştığınızda onların yöntemi çağrılır. Ben şahsen bunu bir adım daha ileri götürmeyi ve özniteliğin __dict__tam olarak onu gölgelediği için normal yollarla erişilemez olduğu örnekteki temel niteliğin gerçek değerini gizlemeyi seviyorum .

Bunun sihir (çift alt çizgi) yöntemleri için anlamsız olduğunu da aklınızda bulundurmalısınız . Bu şekilde sihirli yöntemler elbette geçersiz kılınabilir, ancak onları kullanan işlemler yalnızca türe bakar. Örneğin, __contains__örneğinizde özel bir şeye ayarlayabilirsiniz , ancak çağrı x in instancebunu dikkate almaz ve type(instance).__contains__(instance, x)onun yerine kullanır. Bu, Python veri modelinde belirtilen tüm sihirli yöntemler için geçerlidir .


1
Kabul edilen cevap bu olmalı: temiz ve Python 3'te çalışıyor.
BlenderBender

@BlenderBender. Desteğiniz için minnettarım
Mad Physicist

Bu cevabın ve seninkinin @ Harhal Dhumai'den yukarıdaki cevabı arasındaki fark nedir?
1313e

1
@ 1313. İşlevsel olarak çok fazla fark olmamalı. Yukarıdaki cevabın girdi olarak daha geniş bir çağrılabilir çağrıyı kabul edebileceğinden şüpheleniyorum, ancak belgeleri okumadan ve etrafta oynamadan emin değilim.
Mad Physicist

Bunun için Python 2 dokümanları: docs.python.org/2/howto/descriptor.html#functions-and-methods . Yani teorik olarak @ Harşal Dhumai'nin cevabı ile aynı.
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İhtiyaç duyarsanız, fonksiyonun içindeki değişkeni kullanabilirsiniz . Yöntemi yalnızca bu tek örnek nesne için geçersiz kıldığınız için, bu yol daha basittir ve kullanmakla tamamen aynı etkiye sahiptir self.


1
Orijinal imzayı kullanan IMHO, özellikle işlev örneğin yakınında değil kodda başka bir yerde tanımlanmışsa okunabilirliğe katkıda bulunur. İstisna, geçersiz kılma yönteminin bir işlev olarak bağımsız olarak kullanıldığı durum olabilir. Elbette, bu basit örnekte önemli değil.
codelogic

1
Bunun neden kabul edilen cevap olmadığını anlamıyorum. Bu denir patchingve bunu yapmanın doğru yolu budur (örneğin boby = Dog()ve boby.bark = new_bark). Kontroller için birim testinde inanılmaz derecede faydalıdır . Daha fazla açıklama için bkz. Tryolabs.com/blog/2013/07/05/run-time-method-patching-python (örnekler) - hayır, bağlantılı site veya yazara bağlı değilim.
Geoff

5
new_bark yönteminin kendine (örnek) erişimi yoktur, bu nedenle kullanıcının new_bark'ta örnek özelliklerine erişmesi mümkün değildir. Bunun yerine, türler modülünden MethodType kullanmanız gerekir (aşağıdaki cevabıma bakın).
Harshal Dhumal

3

functools.partialBurada kimse bahsetmediğinden :

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'da işlevler birinci sınıf nesneler olduğundan, sınıf nesnenizi başlatırken bunları iletebilir veya belirli bir sınıf örneği için istediğiniz zaman geçersiz kılabilirsiniz:

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()

ve sonuçlar

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

-4

S. Lott'un kalıtım fikrini sevmeme ve 'tip (a)' konusuna katılmama rağmen, işlevlerin de erişilebilir nitelikleri olduğundan, bunun şu şekilde yönetilebileceğini düşünüyorum:

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__

ve çıktı:

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
"Yönetilmesi" gerekiyorsa, o zaman - bana göre - bir sorun var. Özellikle de işi zaten yapan birinci sınıf bir dil özelliği olduğunda.
S.Lott

-4

Sevgili, bu geçersiz kılmıyor, sadece aynı işlevi nesne ile iki kez çağırıyorsunuz. Temelde geçersiz kılma birden fazla sınıfla ilgilidir. aynı imza yöntemi farklı sınıflarda mevcut olduğunda, hangi işlevi çağırdığınızı, bunu kimin çağırdığına karar verin. Python'da, birden fazla sınıf oluşturduğunuzda geçersiz kılma mümkündür, aynı işlevleri yazar ve python'da bu aşırı yüklemeye izin verilmeyen bir şey daha paylaşılır.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.