面向对象三大特性之多态
多态
面向对象的最后一个特性是多态。多态指的是一个事物的具有不同的形态,即多种形态。比如,动物这个类:可以具体表现为猫类,狗类,猪类。
class Animal: #同一类事物:动物
def talk(self):
pass
class Cat(Animal): #动物的形态之一:猫
def talk(self):
print('喵喵喵')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('哼哼哼')
#实例化得到三个对象
>>> cat=Cat()
>>> dog=Dog()
>>> pig=Pig()
多态的好处是时多态性。
多态性
多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象应该有的属性。
这就需要在设计时,把对象的使用方法统一成一种:例如cat、dog、pig都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用。
cat.talk() # 喵喵喵
dog.talk() # 汪汪汪
pig.talk() # 哼哼哼
更进一步,我们可以定义一个统一的接口来使用
def talk(obj):
obj.talk()
talk(cat)
talk(dog)
talk(pig)
python是一门面向对象的编程语言,它本身就支持多态性。比如,我们使用的len()
、for循环
这样做的好处是增强了程序的灵活性和可扩展性。
# len 可以获取很多数据类型的长度,因为这些数据类型都有一个内置的__len__()属性 len()是接口
# for循环可以循环遍历可迭代数据类型的元素,是因为这个数据类型都有一个__iter__()属性 iter()是接口
abc模块
综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象。上面我们是通过继承实现多态性,每个类都继承Animal类,Animal类面定义了talk方法。我们知道,子类可以选择定义talk也可以选择不定义talk方法。如果想要任何一个新继承的子类都有自己的talk方法,那它就必须自己定义一个。可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名。此时我们需要用到abc模块(abstract class)
抽象类(了解)
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,抽象类本身不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self): # 抽象方法中无需实现具体的功能
pass
class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self): # 必须定义talk方法
pass
cat=Cat() # 若子类中没有一个定义talk的方法则会抛出异常TypeError,无法实例化
鸭子类型
鸭子类型(duck typing)指的是只要走路像鸭子,叫声也像鸭子,那他就是鸭子。
上面的多态性我们是利用继承实现的,其实本质上可以实现使用的便利是因为每个类都有一个相同的方法。
所以,我们完全可以不借助继承就可以试下多态性。具体做法是在每一个类里面都自觉地、统一地定义一个相同的名字的方法。
class Cat(Animal): #动物的形态之一:猫
def talk(self):
print('喵喵喵')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('汪汪汪')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('哼哼哼')
#实例化得到三个对象
>>> cat=Cat()
>>> dog=Dog()
>>> pig=Pig()
def talk(obj):
obj.talk()
上面这种方式就是鸭子类型,每一类长得像、功能也像。那他们就是一类的,就可以使用统一的接口。
比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,且这是一种自觉的,不强制使用方式的思维。正因为如此,Python才崇尚的“鸭子类型”。