在实例级别覆盖方法


87

Python中是否有一种方法可以在实例级别覆盖类方法?例如:

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

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

Answers:


11

请不要这样做,如图所示。当您实例化与该类不同的实例时,代码变得不可读。

您无法调试monkeypatched代码。

当您在boby和中发现错误时print 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我认为您应该将“显示的”链接到实际答案,我真的没有问题,因为这个答案是可以接受的,但是在某些情况下和我的略读中,有一些原因让您需要猴子打补丁我用“如图所示”来表示您正在显示的内容,而不是不同的答案。
Daniel Chatfield 2013年

4
子类化是比猴子修补方法强得多的契约。可能的话,强有力的合同可以防止意外行为。但是在某些情况下,需要更宽松的合同。在这种情况下,我宁愿使用回调(而不是猴子补丁),因为通过允许自定义某些行为,类契约不会受到侵犯。您的答案尽管有实际建议,但对于该问题而言却很差,并且编码风格也不一致。
Aaron3468'9

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)时,在foo .__ dict__中添加名称== bark和实例方法Dog.new_bark对吗?因此,当您再次调用它时,它将首先查找实例字典并进行调用,
James

11
请说明其作用,尤其是其funcType作用和必要性。
亚历山大·杜宾斯基

3
我认为可以通过使用funcType = types.MethodType(导入后types)代替来使此操作更简单,更明确funcType = type(Dog.bark)
Elias Zamaria '16

13
我猜想这不适用于Python3。我收到“ TypeError:function()参数1必须是代码,而不是函数”错误。对Python 3有什么建议吗?
Sait的

2
您也可以调用__get__该函数将其绑定到实例。
疯狂物理学家

37

您需要使用 MethodTypetypes模块。的目的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树皮(个体):
        打印“ Woof”

def new_bark():
    打印“ Woof Woof”

foo = Dog()

#“天窗”
foo.bark()

#仅将new_bark替换为该对象的树皮
foo.bark = new_bark .__ get __(foo,Dog)

foo.bark()
#“ Woof Woof”

通过将绑定方法分配给实例属性,您已经创建了覆盖方法的几乎完整的模拟。缺少的一个方便的功能是访问的no-arg版本super,因为您不在类定义中。另一件事是,__name__绑定方法的属性不会像在类定义中那样使用要覆盖的函数的名称,但是您仍然可以手动设置它。第三个区别是您的手动绑定方法是一个纯属性引用,恰好是一个函数。该.运营商不只是获取该引用。另一方面,当从实例调用常规方法时,绑定过程每次都会创建一个新的绑定方法。

顺便说一句,这样做的唯一原因是实例属性覆盖了非数据描述符。数据描述符具有__set__方法,而方法(幸运的是您)没有。实际上,该类中的数据描述符优先于任何实例属性。这就是为什么您可以分配一个属性的原因:__set__尝试进行分配时,将调用其方法。我个人更进一步,将实例属性的基础属性的实际值隐藏在实例中__dict__,而正是由于属性遮盖了该属性,因此通常无法使用该值。

您还应该记住,这对于魔术(双下划线)方法毫无意义。魔术方法当然可以用这种方法来覆盖,但是使用它们的操作只看类型。例如,您可以__contains__在实例中设置一些特殊的内容,但是调用x in instance将忽略该内容,type(instance).__contains__(instance, x)而是使用它。这适用于Python数据模型中指定的所有魔术方法。


1
这应该是公认的答案:它很干净并且可以在Python 3中运行
。– BlenderBender

@BlenderBender。感谢您的支持
疯狂物理学家

这个答案和@Harshal Dhumai的答案相比有什么区别?
1913年

1
@ 1313。从功能上讲,应该没有太大的区别。我怀疑上面的答案可能接受更广泛的可调用对象作为输入,但是如果不阅读文档并随便玩耍,我不确定。
疯狂物理学家,

与此相关的Python 2文档:docs.python.org/2/howto/descriptor.html#functions-and-methods。因此,从理论上讲,它与@Harshal Dhumai的答案相同。
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如果需要,可以在函数内使用变量。由于您仅针对一个实例对象覆盖方法,因此这种方法更简单,并且具有与using完全相同的效果self


1
恕我直言,使用原始签名可增加可读性,尤其是如果函数在代码中的其他位置(而不是在实例附近)定义时。例外的情况是,替代方法也独立用作功能。当然,在这个简单的例子中,没关系。
codelogic

1
我不明白为什么这不是公认的答案。这被称为,patching并且这是正确的方法(例如boby = Dog()boby.bark = new_bark)。在控件的单元测试中,它非常有用。有关更多说明,请参见tryolabs.com/blog/2013/07/05/run-time-method-patching-python(示例)-否我不隶属于链接的网站或作者。
Geoff

5
new_bark方法无权访问自身(实例),因此用户无法访问new_bark中的实例属性。相反,需要使用来自类型模块的MethodType(请参阅下面的答案)。
Harshal Dhumal

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

-4

亲爱的,这并不能覆盖您只是用该对象两次调用同一函数。基本上,压倒性涉及多个类别。当相同的签名方法存在于不同的类中时,您要调用的函数将决定调用此函数的对象。当您使多个类写入相同的函数并且需要分享一件事时,可以在python中进行覆盖,还有一点要分享,即python中不允许重载


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.