Python多态¶
多态(Polymorphism), 字面意思是”有多种形态”.
这大致意味着即便你不知道变量指向的是哪种对象, 也能够对其执行操作, 且操作的行为将随对象所属的类型而已. 具体的说, 就是多个不同类型的对象都实现了相同的方法, 可以通过一个变量调用不同对象的相同方法, 而无需关心这个变量当前绑定的到底是哪一个对象.
每当无需知道对象是什么样的就能对其执行操作时, 都是多态在起作用. 这不仅仅适用与类方法, 内置运算符和函数也大量使用了多态. 例如:
>>> 1 + 2
3
>>> 'Fish' + 'license'
'Fishlicense'
上述代码表明, 加法运算符(+)既可用于数, 也可用于字符串.
Python中的多态形式又称为鸭子类型, 这个术语源自说法: “如果走起来像鸭子, 叫起来像鸭子, 那么它就是鸭子.” 意思是, 只关心一个对象是否实现了要求的方法, 而不关心其类型. 一个对象, 只要实现了要求的方法, 那么它就是一种形态.
这和C++的多态不同, C++的多态有严格的类型要求, 必须是从共同父类派生而来的子类, 才能实现多态.
注解
在Python中, 一个对象只要实现了要求的方法, 就可以将其视为多态的一种形态;
而在C++中, 对象必须首先满足类型要求, 才能作为多态的一种形式.
接口和内省¶
接口, 就是对外暴露的属性和方法. 处理多态对象时, 只关心其接口.
在Python中, 不需要显式指定对象必须包含哪些方法才能用作参数. 例如, 不需要像Java那样显式编写接口, 而是假定对象能够完成要求的任务. 如果不能完成, 程序将失败.
可以直接调用对象的特定方法, 如果对象没有该方法将引发异常. 也可以不是直接调用方法并期待一切顺利, 而是检查所需的方法是否存在; 如果不存在, 就改弦易辙.
可以使用内置函数hasattr()判断某个对象是否包含指定的方法.
抽象基类¶
在历史上的大部分时间内, Python几乎都只依赖于鸭子类型, 即假设所有对象都能完成其工作, 同时偶尔使用hasattr来检查所需的方法是否存在.
很多其它语言(如Java和Go)都采用显式指定接口的理念, Python也通过引入abc模块提供了这种理念的实现, 这个模块为所谓的抽象基类提供了支持.
一般而言, 抽象类是不能(至少是不应该)实例化的类, 其职责是定义子类应该实现的一组抽象方法.
抽象基类让我们能够本着鸭子类型的精神使用这种实例检查. 我们不关心对象是什么(对象的类型), 只关心对象能做什么(它实现了哪些方法).
注解
抽象基类提供了一种保障, 保证了其子类中实现了要求的方法.
Example:
from abc import ABC, abstractmethod
# 定义一个抽象基类
class Talker(ABC):
# 定义一个抽象方法
@abstractmethod
def talk(self):
pass
# 定义一个抽象基类的子类
class Kingget(Talker):
def talk(self):
print('Ni!')
>>> k = Kingget()
>>> isinstance(k, Talker)
True
在子类中必须重写抽象方法, 否则实例化时会报错.
内置函数isinstance()可以用来检查一个对象是否是一个类的实例.
可以使用该函数来判断一个对象是否是一个抽象基类的实例, 判断该对象是否实现了要求的方法, 从而更好的使用多态.
但是, 如果有的类实现了所要求的方法, 但是没有从抽象基类继承, 那么该类的实例就无法通过isinstance()对抽象基类的检查;
为解决这个问题, 可以将该类注册为抽象基类的类型, 这样就能通过isinstance()的检查了.
Example:
from abc import ABC, abstractmethod
# 定义一个抽象基类
class Talker(ABC):
@abstractmethod
def talk(self):
pass
# 创建另一个类
class Herring:
def talk(self):
print('Blub.')
>>> h = Herring()
>>> isinstance(h, Talker)
False
如上所示, Herring类实现了抽象基类Takler中定义的方法talk(), 根据Python鸭子类型的精神, Herring应该也是多态的一种形态.
但是, Herring类的实例h不能通过isinstance(h, Talker)检查, 因为Herring类没有继承抽象基类Talker.
为此, 可以将Herring类注册为Talker类型, 这样就可以通过检查了.
>>> Talker.register(Herring)
>>> h = Herring()
>>> isinstance(h, Talker)
True
但是这样做存在一个缺点, 就是直接从抽象类派生提供的保障没有了.
标准库提供的抽象类¶
标准库(如collections.abc)提供了多个有用的抽象类.