我知道通过继承基类。基类中的所有函数也可以在派生类中访问。但是它如何以另一种方式工作,这意味着可以在基类中访问子类中定义的函数。
我用一个例子尝试了上面的内容。它工作得很好。但那怎么可能呢。我无法得到工作背后的逻辑。
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传递,但子类的详细信息在逻辑上不能在基类中访问,对吗?
答案 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._colour
或catfish.__init__
中设置tunafish.__init__
。