基类中派生类的范围 - python中的继承

时间:2018-03-24 15:17:47

标签: python inheritance base-class

我知道通过继承基类。基类中的所有函数也可以在派生类中访问。但是它如何以另一种方式工作,这意味着可以在基类中访问子类中定义的函数。

我用一个例子尝试了上面的内容。它工作得很好。但那怎么可能呢。我无法得到工作背后的逻辑。

class fish:

    def color(self):
        # _colour is a property of the child class. How can base class access this?
        return self._colour  


class catfish(fish):

    _colour = "Blue Cat fish"

    def speed(self):
        return "Around 100mph"

    def agility(self):
        return "Low on the agility"

class tunafish(fish):

    _colour = "Yellow Tuna fish"

    def speed(self):
        return "Around 290mph"

    def agility(self):
        return "High on the agility"

catfish_obj = catfish()
tunafish_obj = tunafish()

print(catfish_obj.color())
print(tunafish_obj.color())

我理解该实例正在通过self传递,但子类的详细信息在逻辑上不能在基类中访问,对吗?

2 个答案:

答案 0 :(得分:3)

您正在访问实例上的属性,不在类上。您的self引用永远不是fish类的实例,只是两个派生类中的一个,并且这些派生类设置了_colour属性。

如果您创建了fish()本身的实例,则会收到属性错误,因为该实例不会设置属性。

您可能认为在基类中,self成为基类的实例;事实并非如此。

相反,实例上的属性直接在实例上查找,在其类和基类上。因此,self._colour会查看type(instance)处的实例以及type(instance).__mro__中所有其他对象,即方法解析顺序,以线性顺序设置层次结构中的所有类。

您可以打印出对象的type()

>>> class fish:
...     def color(self):
...         print(type(self))
...         return self._colour
...
# your other class definitions
>>> print(catfish_obj.color())
<class '__main__.catfish'>
Blue Cat fish
>>> print(tunafish_obj.color())
<class '__main__.tunafish'>
Yellow Tuna fish

self引用是派生类的实例,传递给继承的方法。因此,self._colour会首先查看self上直接设置的属性,然后是type(self),然后找到_colour

也许有助于了解Python方法的工作原理。方法只是函数的精简包装器,当您在实例上查找属性时创建:

>>> tunafish_obj.color  # access the method but not calling it
<bound method fish.color of <__main__.tunafish object at 0x110ba5278>>
>>> tunafish.color      # same attribute name, but on the class
<function fish.color at 0x110ba3510>
>>> tunafish.color.__get__(tunafish_obj, tunafish)  # what tunafish_obj.color actually does
<bound method fish.color of <__main__.tunafish object at 0x110ba5278>>
>>> tunafish_obj.color.__self__   # methods have attributes like __self__
<__main__.tunafish object at 0x110ba5278>
>>> tunafish_obj.color.__func__   # and __func__. Recognise the numbers?
<function fish.color at 0x110ba3510>

仔细查看我访问的对象的名称,以及在函数上调用__get__方法时会发生什么。当您访问实例上的某些属性时,Python使用名为 binding 的进程;当您以这种方式访问​​属性,并指向具有__get__方法的对象时,该对象称为描述符,并调用__get__来绑定对象无论你看起来对象是什么。请参阅descriptor howto

在实例上访问color,生成一个绑定方法对象,但该对象的描述告诉我们它来自fish,它被命名为a * 实例引用的绑定方法fish.color。在类上访问相同的名称会为我们提供fish.color 函数,我可以手动绑定它以再次创建方法。

最后,该方法具有属性__self__,它是原始实例,__func__是原始函数。而且有魔力,当你调用一个绑定方法时,方法对象只调用__func__(__self__, ....),所以传入它绑定的实例。

当该函数被继承时,(在fish类上找到,fish.color),仍然传递派生类的实例,并且仍然拥有一切派生类有。

Python非常动态,非常灵活。你可以使用任何旧函数并将它放在一个类上,它可以绑定到一个方法中。或者,您可以使用任何未绑定的功能,并手动传入具有正确属性的对象,并且只需工作。 Python并不关心,真的。所以你可以传入一个新的,独立的对象类型,并且仍然可以使用fish.color函数:

>>> fish.color  # original, unbound function on the base class
<function fish.color at 0x110ba3510>
>>> class FakeFish:
...     _colour = 'Fake!'
...
>>> fish.color(FakeFish)  # passing in a class! Uh-oh?
<class 'type'>
'Fake!'

因此,即使传入与fish层次结构完全无关但具有预期属性的类对象,仍然有效。

对于大多数Python代码,如果它像鸭子一样走路,并且像鸭子一样嘎嘎叫,代码将接受它作为鸭子。称之为duck typing

答案 1 :(得分:0)

派生类的方法在基类中不可用。但是,对于在特定对象上运行的任何函数,共享字段self._colour指的是您呼叫_colour的对象中color()的值,无论_colour的设置方式如何。

修改,因为您在课程中直接设置了_colour = ...,在功能之外,任何catfish都会有_colour == "Blue Cat fish"和任何tunafish将有_colour == "Yellow Tuna fish"。这些值虽然在类上设置,但在每个实例中都可用。这就是self._colour工作的原因,即使你从未直接说self._colour = ...。如果您想要特定于鱼类的颜色,则需要在self._colourcatfish.__init__中设置tunafish.__init__