基础 class 中的派生范围 class - python 中的继承

Scope of derived class in base class - Inheritance in python

我知道是通过继承基础 class。 base class 中的所有函数也可以在派生 class 中访问。但是它如何以另一种方式工作,这意味着在子 class 中定义的函数可以在基 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())

我知道实例正在通过自己传递,但是子 class 的详细信息在逻辑上应该无法在基 class 中访问,对吗?!

您正在访问一个实例的属性,而不是 class。您的 self 引用永远不是 fish class 的实例,只是两个派生 classes 之一的实例,而那些派生的 classes 设置 _colour属性。

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

你可能会认为在基classes中,self成为基class的一个实例;事实并非如此。

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

您可以打印出对象的 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 引用是派生的 classes 的实例,传递给继承的方法。所以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__ 方法的对象时,该对象被称为 descriptor,并且 __get__ 被称为将对象绑定到您查看该对象的任何对象。见 descriptor howto.

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

最后,该方法有一个属性__self__,这是原始实例,__func__是原始函数。神奇的是,当您调用绑定方法时,方法对象只调用 __func__(__self__, ....),因此传入它绑定的实例。

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

Python 非常动态,非常 灵活。您可以采用任何旧函数并将其放在 class 上,并且可以将其绑定到方法中。或者您可以采用任何未绑定的函数,并手动传入具有正确属性的对象,它会正常工作。 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!'

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

对于大多数 Python 代码,如果它走路像鸭子,叫起来像鸭子,代码将接受 它是鸭子。称之为 duck typing.

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

Edit 因为你直接在 class 中设置 _colour = ...,在函数之外,任何 catfish 都会有 _colour == "Blue Cat fish" 和任何 tunafish 将有 _colour == "Yellow Tuna fish"。这些值虽然是在 class 上设置的,但在每个实例中都可用。这就是为什么即使您从未直接说 self._colour = ...self._colour 仍然有效。如果你想要 fish-specific 颜色,你需要在 catfish.__init__tunafish.__init__.

中设置 self._colour