Python笔记 Day19 (抽象类,接口类,多态,鸭子类型)

昨日复习

  派生方法和派生属性

    super想使用子类的对象调用父类的方法时,才使用super

    super在类里:super().方法名(参数) == 指名道姓 :父类名.方法名(self,参数)

  

  多继承  钻石继承

    经典类:Python2 不继承object,查找名字遵循深度优先遍历算法

    新式类:Python3中都是新式类,类名.mro()方法查找广度优先顺序,super方法就是根据这个顺序查找的


一、抽象类

  1、什么是抽象类?

    与java一样,Python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

  2、为什么要有抽象类?

    如果说类是从一堆对象中抽取相同的内容而来,那么抽象类就是从一堆类中抽取相同的内容而来,内容包括数据属性和函数属性。

  3、从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

    从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案。

#举例
class Alipay(object):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)
 
class QQpay(object):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(100)

#执行结果
使用支付宝支付了100元
使用qq支付了100元

归一化设计:

# 支付宝和qq有相同的方法,可不可以定义一个方法,统计实现呢?
class Alipay(object):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)
 
class QQpay(object):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(object):
    def fuqian(self,money):
        print('使用微信支付了%s元' % money)
 
def pay(obj,money):  # 统一支付方法
    '''
    :param obj: 实例化对象
    :param money: 金钱
    :return: 使用xx支付了xx元
    '''
    obj.pay(money)  # 执行实例化对象的类方法pay
 
a = Alipay()
#a.pay(100)
b = QQpay()
#b.pay(100)
 
pay(a,100)  # 传入一个对象和100
pay(b,100)

#执行结果:
使用支付宝支付了100元
使用qq支付了100元

  以上方法就是归一化设计:不管是哪一个类的对象,都调用同一个函数完成相似的功能!归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合。

  比如Python的len方法,它可以接受很多参数类型,返回对象(字符串,列表,元组等)长度或则项目个数。

len() 和 __len__():

  也就是说,只有一个类中实现了__len__()方法,才能使用len() 方法。因为len() 就想上例中调用了各个对象的和__len__()方法的归一化。

a = '123'
print(a.__len__())
print(len(a))  # 此方法只是对__len__()的一个归一化设计

再如上例中:

a = Alipay()
#a.pay(100)
b = QQpay()
#b.pay(100)
w = Wechatpay()
 
pay(a,100)  # 传入一个对象和100
pay(b,100)
pay(w,100)

执行报错:
AttributeError: 'Wechatpay' object has no attribute 'pay'
提示没有pay方法。why?
把fuqian改成pay就可以了

为了避免这种问题,创建一个规范类Payment

然后每个类继承以下:

class Payment(object):  # 抽象类 接口类 规范
    def pay(self):pass
 
class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)
 
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付了%s元' % money)


#Payment不需要pay的代码,只需要子类实现即可
#Payment没有任何代码实现,必须要求子类有同名的方法名

w = Wechatpay()
w.pay(100)
# TypeError: pay() takes 1 positional argument but 2 were given

  假如有5个类,需要规范一下,怎么保证,实例化的时候,有不规范的,直接报错呢?

#需要用到abc模块

from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass  # 抽象方法,python从语法上支持的规范类
 
class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)
 
class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):  # 继承了Payment类,就必须去实现被abstractmethod装饰的方法
    def fuqian(self,money):
        print('使用微信支付了%s元' % money)
 
w = Wechatpay()  # 实例化

#执行输出:
TypeError: Can't instantiate abstract class Wechatpay with abstract methods pay

  抽象类和接口类做的事情:建立规范

    制定一个类的metaclass是ABCMeta,那么这个类就变成了一个抽象类(接口类),这个类的主要功能就是建立一个规范,抽象类中所有被abstractmethod装饰的方法都必须被继承的子类实现,如果不实现,那么在实例化阶段将会报错,无论是抽象类还是接口类metaclass=ABCMeta 都不可以被实例化。

p = Payment() 报错

#除非把 fuqian 改成统一的 pay 才能被实例

如果抽象类没有abstractmethod装饰器,那么这个方法,子类不需要实现,把fuqian改成pay就可以实例化了

依赖倒置原理:

  高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程 

  解释一下, 依赖倒置原则

    Payment是高层模块,Wechatpay是底层类。Wechatpay只能依赖Payment,Payment不能依赖Wechatpay,因为它是底层模块。

    Payment是抽象类,它定义了pay方法,具体实现的逻辑,它不需要写,由子类来完成。

写代码并不是为了实现功能,而是在实现功能的前提下,规划化代码。


二、接口类

  什么是接口?(就好比插板的插口)

    python里面没有接口的概念,那接口是哪儿来的概念呢?从java里面来的。

    接口类:定义一个接口对继承类进行约束,接口里面有方法,继承类就必须有什么方法,接口中不能有任何功能代码。

比如动物园里面的动物,会游泳,走路,爬树,飞行的动物,比如老虎,青蛙,天鹅,猴子。

# 定义几个动物
class Tiger(object):  # 老虎
    def walk(self):pass  # 走路
    def swim(self):pass  # 游泳
class Monkey(object):  # 猴子
    def walk(self):pass
    def climb(self):pass  # 爬树
class Swan(object):  # 天鹅
    def walk(self): pass
    def swim(self): pass
    def fly(self):pass  # 飞行
    def cal_flying_seeed(self):pass  # 计算飞行速度
    def cal_flying_height(self):pass  # 计算飞行高度

观察上面代码,技能重复了。这样写,容易丢失方法,

加一个鹦鹉,但是它少了一些飞行类的方法:

class Parrot(object):
    def fly(self):pass
    def cal_flying_seeed(self): pass

怎么解决这个问题?定义一个抽象A

from abc import ABCMeta,abstractmethod
class A(metaclass=ABCMeta):
    @abstractmethod
    def fly(self): pass
    @abstractmethod
    def cal_flying_seeed(self): pass
    @abstractmethod
    def cal_flying_height(self): pass
class Tiger(object):  # 老虎
    def walk(self):pass  # 走路
    def swim(self):pass  # 游泳
class Monkey(object):  # 猴子
    def walk(self):pass
    def climb(self):pass  # 爬树
class Swan(A):  # 天鹅
    def walk(self): pass
    def swim(self): pass
    def fly(self):pass  # 飞行
    def cal_flying_seeed(self):pass  # 计算飞行速度
    def cal_flying_height(self):pass  # 计算飞行高度
class Parrot(A):
    def fly(self):pass
    def cal_flying_seeed(self): pass
     
Parrot()

#执行输出:TypeError: Can't instantiate abstract class Parrot with abstract methods cal_flying_height
也就是说Parrot类里面,全部至少有fly、cal_flying_seeed、cal_flying_height,才不会报错!
鹦鹉实例化时,报错,找不到方法cal_flying_height这样就约束了飞行动物的方法 所有会走的动物,具有一些会走的动物特性 对于爬行动物,不能继承A所以需要再定义一抽象类

所以需要再定义一个抽象类:

from abc import ABCMeta,abstractmethod
class FlyAnimal(metaclass=ABCMeta):  # 飞行
    @abstractmethod
    def fly(self):pass
    @abstractmethod
    def cal_flying_speed(self):pass
    @abstractmethod
    def cal_flying_height(self):pass
class WalkAnimal(metaclass=ABCMeta):  # 走路
    @abstractmethod
    def walk(self):pass
class SwimAnimal(metaclass=ABCMeta):  # 游泳
    @abstractmethod
    def swim(self):pass
class Tiger(WalkAnimal,SwimAnimal):  # 老虎,继承走路和游泳
    def walk(self):pass  # 走路
    def swim(self):pass  # 游泳
class Monkey(WalkAnimal):  # 猴子
    def walk(self):pass
    def climb(self):pass  # 爬树
class Swan(FlyAnimal,WalkAnimal,SwimAnimal):  # 天鹅,继承飞行,走路,游泳
    def walk(self): pass
    def swim(self): pass
    def fly(self):pass  # 飞行
    def cal_flying_speed(self):pass  # 计算飞行速度
    def cal_flying_height(self):pass  # 计算飞行高度
class Parrot(FlyAnimal):  # 鹦鹉,继承飞行
    def fly(self):pass
    def cal_flying_speed(self): pass
    def cal_flying_height(self): pass
 
#实例化
Tiger()
Monkey()
Swan()
Parrot()

#正确

接口隔离原则:

  使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。

  解释:在上面的动物园的例子中:

      所有会飞的动物 具有一些会飞的动物的特性

      所有会走的动物 具有一些会走的动物的特性

      不能使用单一的总接口来完成,所以需要定义多个抽象类,同时,不需要的接口不要给底层类继承

总结:

  接口类的作用:

    在java中,能够满足接口隔离原则,且完成多继承的约束。

    在Python中,满足接口隔离原则,由于Python本身支持多继承,所以就不需要接口的概念。

  

  抽象类和接口类:

    1、Python中:

      并没有什么不同,都是用来约束子类中的方法的,只要是抽象类和接口类中被abstractmethod装饰的方法,都需要被子类实现。

      需要注意的是,当多个类之间有相同的功能也有不同的功能的时候,应该采用多个接口类来进行分别的约束

     2、java中:

         抽象类和接口截然不同,抽象类的本质还是一个类,是类就必须遵循单继承的规则,所以一个子类如果被抽象类约束,那么它只能被一个父类控制,当多个类之间相同的功能的时候,java只能用接口来解决问题。

#面试题
 他可能会问:什么是抽象类?什么是接口类?
  抽象类 是python中定义类的一种规范,用来约束子类中的方法的。被abstractmethod装饰的方法,子类必须实现,否则实例化时报错。
  接口类 满足接口隔离原则,且完成多继承的约束。如果不按照规范,在调用方法时,报错。

三、多态

  多态:指的是一类事物有多种形式。

#动物有多种形态:人、狗、猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass
 
class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')
 
class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')
 
class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

多态性:
  什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性),多态性是指在不考虑实例类型的情况下使用实例。

class Payment:
    def pay(self):pass
 
class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):
    def pay(self, money):
        print('使用微信支付了%s元'%money)
 
def pay(pay_obj,money):  # 统一支付方法
    '''
    :param pay_obj: 实例化对象
    :param money: 金钱
    :return: 使用xx支付了xx元
    '''
    pay_obj.pay(money)  # 执行实例化对象的类方法pay
 
qq = QQpay()
we = Wechatpay()
 
pay(qq, 100)
pay(we, 200)

#执行输出:
使用qq支付了100元
使用微信支付了200元

# 通过执行pay函数,传入不同的参数返回不同的结果,这就是多态,也就是一种接口,多种实现 

  听海峰老师说,其实Python的继承和多态其实是一个概念,只是多次调用了同一种方法,并且

得到不同结果,也就成了多态。

# 上面的例子是面向过程的函数加面向对象,实现的。整形效果不好,把函数放到父类里面
class Payment:
    @staticmethod
    def pay(pay_obj,money): # 静态方法.需要通过类名+方法名来调用这个方法
        '''
        统一支付方法
        :param pay_obj: 实例化对象
        :param money: 金钱
        :return: 使用xx支付了xx元
        '''
        pay_obj.pay(money)
         
class QQpay(Payment):
    def pay(self, money):
        print('使用qq支付了%s元'%money)
 
class Wechatpay(Payment):
    def pay(self, money):
        print('使用微信支付了%s元'%money)
         
qq = QQpay()
we = Wechatpay()
 
Payment.pay(qq, 100)
Payment.pay(we, 200)

执行输出:

使用qq支付了100元
使用微信支付了200元

  总结:

    1、在python2种,虽然没有有object类,但是它有一个类似于object类的属性。它内嵌在解释器里面,你看不到,它没开放。python3开放了,它就是objcet

    2、多态 通过继承实现,在Python中不需要可以实现多态,行为Python本身自带多态效果


四、鸭子类型

  逗比时刻:
    Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

    python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象,
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1、利用标准库中定义的各种方法,‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法。

例2、序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass
 
    def write(self):
        pass
 
class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

在Python不崇尚通过继承来约束 
比如:
[].index()
''.index()
().index()

查看index源码:
def index(self, value, start=None, stop=None): # real signature unknown; restored from __doc__
    """
    L.index(value, [start, [stop]]) -> integer -- return first index of value.
    Raises ValueError if the value is not present.
    """
    return 0

#index这个名字是约定俗成的,它不是代码级别约束的

总结:

  多态 通过继承实现
    java 在一个类之下发展出来的多个类的对象都可以作为参数传入一个函数或者方法
    在python中不需要刻意实现多态,因为python本身自带多态效果
  鸭子类型
    不是通过具体的继承关系来约束某些类中必须有哪些方法名,
    是通过一种约定俗成的概念来保证在多个类中相似的功能叫相同的名字

猜你喜欢

转载自www.cnblogs.com/double-W/p/9787200.html