如何处理从基类方法重写的方法调用?

26
根据继承文档
派生类可以重写其基类的方法。因为在调用同一对象的其他方法时,方法没有特殊权限,所以调用在同一基类中定义的另一个方法的基类方法可能会调用覆盖它的派生类的方法。
发生这种情况是如何的?有人能用简单的例子说明这个概念吗?

从帮助中心:如果您的问题可以通过整本书来回答,或者有许多有效答案,那么它可能对我们的格式来说太宽泛了。尝试在最近的谷歌亭中输入python继承。阅读前五个链接,然后就您不理解的具体事项提出具体问题。 - n. m.
10
我认为这是一个很好的问题。作者提出了一个问题,不是关于继承的一般性问题,而是关于Python中一个非常具体的特性的问题。 - IanPudney
5个回答

33

以下是您所请求的示例。这将打印出chocolate

class Base:
    def foo(self):
        print("foo")
    def bar(self):
        self.foo()

class Derived(Base):
    def foo(self):
        print("chocolate")

d = Derived()
d.bar()  # prints "chocolate"

之所以打印出字符串chocolate而不是foo,是因为Derived覆盖了foo()函数。即使bar()Base中被定义,它最终调用的是Derived实现的foo(),而不是Base的实现。


哦,那很简单。为了100%的清晰度,Base类的一个实例的方法永远不会执行Derived类的方法?我原以为文档是在暗示这一点。 - innisfree
@innisfree:你说得对,它们不能。想象一下这样的设置,你有一个基类派生出多个派生类。基类会查看哪个派生类来执行方法呢? - Matthias
3
@d 也是 Base 类的一个实例,这可能是混淆的根源。但是,是的,Base().bar() 不会调用任何重写的方法。 - Bergi
@innisfree 当然,除非你猴子补丁实例。例如,b = Base(); b.foo = Derived.foo.__get__(b); b.bar()。但是,这只会在你明确放入代码时才会发生。 - jpmc26

17

它是如何工作的?

当对类实例进行属性查找时,按照一定顺序搜索该类字典和其基类的字典(参见:方法解析顺序),以寻找适当的方法。被发现的方法会首先被调用。

使用以下 Spam 示例:

class Spam:
    def produce_spam(self):
        print("spam")
    def get_spam(self):
        self.produce_spam()

class SuperSpam(Spam):
    def produce_spam(self):
        print("super spam")

Spam 定义了函数 produce_spamget_spam。它们存储在其 Spam.__dict__ (类命名空间) 中。通过继承,子类 SuperSpam 可以访问这两个方法。SuperSpam.produce_spam 并不替代 Spam.produce_spam,当查找实例中的名称 'produce_spam' 时,它仅是先被找到。

基本上,继承的结果是,如果在子类的字典中未找到属性,则还会搜索任何基类的字典。

当第一次使用函数 get_spam 进行调用时:

s = SuperSpam()
s.get_spam()

事件的顺序大致如下:

  • 查找 SuperSpam__dict__,以获取 get_spam
  • 由于在 SuperSpam__dict__ 中没有找到,所以继续查找其基类(mro 链)的字典。
  • Spammro 链中的下一个位置,因此在 Spam 的字典中找到了 get_spam

现在,在使用 self.produce_spam 查找 produce_spam 时,序列要短得多:

  • 查找 SuperSpamself)的 __dict__,以获取 produce_spam
  • 找到它,获取它并调用它。

produce_spam 最先在 __dict__ 中被找到,因此被提取了出来。


8
class Base():
    def m1(self):
        return self.m2()
    def m2(self):
        return 'base'

class Sub(Base):
    def m2(self):
        return 'sub'

b = Base()
s = Sub()
print(b.m1(), s.m1())

输出 "base sub"。


1
为了说明它是如何工作的,请考虑这两个类:
class Parent(object):
    def eat(self):
        print("I don't want to eat that {}.".format(self.takefrompocket()))

    def takefrompocket(self):
        return 'apple'

    def __getattribute__(self, name):
        print('Looking for:', name)
        method_to_use = object.__getattribute__(self, name)
        print('Found method:', method_to_use)
        return method_to_use

class Child(Parent):
    def takefrompocket(self):
        return 'salad'

__getattribute__方法在新式类中(例如Python3中的所有类)负责属性查找。它只是被实现为打印每个查找所做的事情 - 通常您不希望也不应该自己实现它。如果您真的感兴趣,查找将遵循Python的方法解析顺序(MRO)

>>> some_kid = Child()
>>> some_kid.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Child object at 0x0000027BCA4EEA58>>
Looking for: takefrompocket
Found method: <bound method Child.takefrompocket of <__main__.Child object at 0x0000027BCA4EEA58>>
I don't want to eat that salad.

所以当你想使用 eat 时,它在这个例子中使用的是 Parent.eat。但是 self.takefrompocket 是从 Child 中使用的。
>>> some_parent = Parent()
>>> some_parent.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Parent object at 0x0000027BCA4EE358>>
Looking for: takefrompocket
Found method: <bound method Parent.takefrompocket of <__main__.Parent object at 0x0000027BCA4EE358>>
I don't want to eat that apple.

这里的两种方法都来自Parent。继承类通常不会干扰它们的祖先!


-3
如果你的子类没有实现这个方法,就抛出一个异常!
class Base(object):

    def something (self):
        raise ('Not implemented')

你能否格式化你的代码,并包含原帖想要的最小示例(即包括子类和执行)? - MSeifert
这绝不是回答“它如何发生”的问题。 - Jax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接