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)提供了多个有用的抽象类.