覆盖实例级别的方法

pis*_*hio 74 python

有没有办法在Python中覆盖实例级别的类方法?例如:

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

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
Run Code Online (Sandbox Code Playgroud)

cod*_*gic 149

是的,这是可能的:

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"
Run Code Online (Sandbox Code Playgroud)

  • 我想这不适用于Python 3.我得到一个"TypeError:function()参数1必须是代码,而不是函数"错误.有关Python 3的任何建议吗? (12认同)
  • 请解释一下这是做什么的,特别是`funcType`做什么以及为什么有必要. (10认同)
  • 我认为通过使用`funcType = types.MethodType`(导入`types`之后)而不是`funcType = type(Dog.bark)`,可以使这更简单,更明确. (2认同)
  • 您还可以在函数上调用`__get__`将其绑定到实例。 (2认同)

Har*_*mal 31

您需要在类型模块中使用MethodType.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!!
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案。它在 Python 2 和 3 中都运行良好 (3认同)
  • 如果使用 python 类型,mypy 会引发错误“无法分配给方法”[请参阅相关问题](https://github.com/python/mypy/issues/2427)。目前,解决方法是使用 setattr(boby, "bar", types.MethodType(_bark, boby)` (2认同)

nos*_*klo 25

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!!
Run Code Online (Sandbox Code Playgroud)

boby如果需要,可以在函数内部使用变量.由于您仅为此一个实例对象覆盖该方法,因此这种方式更简单,并且与使用具有完全相同的效果self.

  • new_bark方法无法访问self(实例),因此用户无法访问new_bark中的实例属性.相反,需要在类型模块中使用MethodType(请参阅下面的答案). (5认同)

Mad*_*ist 21

为了解释@ codelogic的优秀答案,我提出了一种更明确的方法.这是一种技术,.当您将类方法作为实例属性访问时,操作员会彻底绑定类方法,除了您的方法实际上是在类之外定义的函数.

使用@ codelogic的代码,唯一的区别在于方法的绑定方式.我使用的事实是函数和方法是Python 中的非数据描述符,并调用该__get__方法.请特别注意原始和替换都具有相同的签名,这意味着您可以将替换作为完整的类方法编写,通过访问所有实例属性self.

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

def new_bark(self):
    print "Woof Woof"

foo = Dog()

# "Woof"
foo.bark()

# replace bark with new_bark for this object only
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 数据模型中指定的所有魔术方法.

  • 这应该是公认的答案:它很干净并且可以在 Python 3 中使用。 (2认同)

use*_*245 13

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!!
Run Code Online (Sandbox Code Playgroud)


S.L*_*ott 12

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

你无法调试monkeypatched代码.

当你发现中的错误bobyprint type(boby),你会看到(一)它是一只狗,但(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!!
Run Code Online (Sandbox Code Playgroud)

  • 我对建议没有异议,也看不出OP.但我认为人们有理由提出要求.或者即使OP没有,其他未来的访问者也可以.所以,恕我直言,答案加上谴责更好,只是一个谴责. (37认同)
  • @arivero:那没有回答我的问题. (11认同)
  • @ S.Lott我认为你应该把"显示"与实际答案联系起来,我真的没有问题,这是接受的答案,但有些理由说明你需要在某些情况下修补补丁和我的脱脂阅读我把"如图所示"表示你所展示的内容,而不是一个不同的答案. (9认同)
  • @arivero:我认为"请不要这样做"如此清晰.您希望看到哪些其他或不同的词语更清楚地表明这不是回答所提出的问题,而是提供有关为什么这是一个坏主意的建议? (7认同)
  • 子类化比猴子修补方法强得多.如果可能,强有力的合同可以防止意外行 但在某些情况下,更宽松的合同是可取的.在这些情况下,我更喜欢使用回调 - 而不是猴子修补 - 因为通过允许自定义某些行为,类合同保持不受管制.你的答案虽然是实用的建议,但对于这个问题却很差,你的编码风格也不一致. (4认同)
  • 您说:“您无法调试猴子补丁代码。” Eclipse和PyDev可以对其进行调试。 (2认同)