4.3-继承-封装-多态


继承-封装-多态

python面向对象的三大特征:继承,封装,多态

  1. 封装:把很多数据封装到一个对象中,把固定功能的代码封装到一个代码块,函数,对象,打包成模块。这都属于封装的思想。具体的情况具体分析:比如,写了一个函数,这个也可以成为封装;在面向对象的思想中,是把一些看似无关紧要的内容组合到一起同一进行存储和使用,就是封装;
  2. 继承:子类可以自动拥有父类中除了私有属性外的其他所有内容;必须现有基类,后有子类。在python中实现继承非常简单:在声明类的时候,在类名后面添加一个小括号,就可以完成继承关系;使用继承的情况:
    1. 从代码层面看:两个类具有相同的功能或者特征的时候,可以采用继承的形式,提取一个父类,这个父类中编写着两个类相同的部分,然后两个类分别去继承这个类就可以了(好处,可以避免写很多重复的功能和代码)。
    2. 从语义去分析:如果语境中出现了x是一种y,这时,y是一种泛化的概念。x比y更具体,这时x就是y的子类。比如:猫是一种动物,猫继承动物。动物能动,猫也能动,这时猫在创建的时候就有了动物的动的属性。
  3. 多态:同一个对象,多种形态。比如:创建一个变量a=10,此时知道a是整数类型,可以通过程序a=’hslm‘,这时,a就成了字符串类型。多态性,就是同一个变量可以是多种形态;

面向对象的继承

继承,可以使子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其或得与父类别不同的功能;另外,为子类别追加新的属性和方法也是常见的做法。

class Person:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

class Dog:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
        
class Cat:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age

继承的用法:

class Aniaml:
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
        
class Person(Aniaml):
    pass

class Dog(Aniaml):
    pass

class Cat(Aniaml):
    pass

# 继承的优点:
#	增加了类的耦合性(耦合性不宜多,宜精)
#	减少了重复代码
#	使得代码更加规范化,合理化

继承的分类

Aminal:叫做父类,基类,超类;

Person,Dog,Cat:子类,派生类;

继承:分为单继承,多继承;

python中类的种类(继承):

  1. python2中有两种:

    1. 经典类(2.2之前(基类的根什么都不写))
    2. 新式类(2.2之后,基类的根是object)
  2. python3只有一种类,就是新式类;

单继承

类名,对象执行父类的方法

# 类名,对象分别调用父类方法
class Aniaml:
    type_name = '动物类'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
    def eat(self):
        print(self)
        print('吃东西')
class Person(Aniaml):
    pass
class Dog(Aniaml):
    pass
class Cat(Aniaml):
    pass

# 类名调用父类方法
print(Person.type_name)     # 动物类
Person.eat(777)
# 777
# 吃东西

# 对象调用父类方法
p1 = Person('白色利穆', '男', 22)    # 实例化对象
print(p1.__dict__)      # {'name': '白色利穆', 'sex': '男', 'age': 22}
print(p1)       # <__main__.Person object at 0x000002B079D97C50>
p1.eat()
# <__main__.Person object at 0x000002B079D97C50>
# 吃东西

执行顺序

# 类名,对象分别调用父类方法
class Aniaml:
    type_name = '动物类'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
    def eat(self):
        print(self)
        print('吃东西')
class Person(Aniaml):
    def eat(self):
        print(f'{self.name}吃饭')
class Dog(Aniaml):
    pass
class Cat(Aniaml):
    pass

# 实例化对象时必须执行__init__法,类中没有,从父类找,父类没有,从object类找
p1 = Person('白色利穆', '男', 22)
p1.eat()
# 先执行自己类中的eat方法,自己类中没有才能执行父类中的方法

同时执行类以及父类方法

如果想要执行父类的func方法,这个方法在子类中也使用了,那就要在子类的方法中写上:父类.func(对象,其他参数)

# 类名,对象分别调用父类方法
class Aniaml:
    type_name = '动物类'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age
    def eat(self):
        print(self)
        print('吃东西')
class Person(Aniaml):
    def __init__(self, name, sex, age, mind):
        '''
        方法一:
        Aniaml.__init__(self, name, sex, age)
        '''
        # 方法二:
        super(Person, self).__init__(name, sex, age)
        self.mind = mind
    def eat(self):
        super().eat()
        print(f'{self.name}吃饭')
class Dog(Aniaml):
    pass
class Cat(Aniaml):
    pass


p1 = Person('黑色利穆', '男', 25, '聪明')
print(p1.__dict__)

单继承练习

# 1
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
class Foo(Base):
    pass
obj = Foo(111)
obj.func1()     # 111 运行的是BASE中的func1


# 2
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
class Foo(Base):
    def func1(self):
        print("Foo. func1", self.num)
obj = Foo(123)
obj.func1() # Foo. func1 123 运行的是Foo中的func1


# 3
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print("Base.func2")
class Foo(Base):
    def func2(self):
        print("Foo.func2")
obj = Foo(123)
obj.func1()
# 123               Base中的func1打印的123
# Foo.func2         Foo中的func2打印的Foo.func2


# 4
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print(111, self.num)
class Foo(Base):
    def func2(self):
        print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
    obj.func2()
# 111,1     Base中的func2
# 111,2     Base中的func2
# 222,3     Foo中的func2

# 5
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print(111, self.num)
class Foo(Base):
    def func2(self):
        print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
    obj.func1()
# 1 \n 111,1        Base中的func1和func2
# 2 \n 111,2        Base中的func1和func2
# 3 \n 222,3        Base中的func1 和 Foo中的func2

多继承

class Xian:
    def fly(self):
        print('神仙会飞')
class Monkey:
    def eat(self):
        print('猴子吃桃')

class SunWuKong(Xian, Monkey):  # 孙悟空是神仙,也是猴
    pass

suk = SunWuKong()
suk.eat()
suk.fly()

# 孙悟空即是一只猴子,也是一个神仙。所以孙悟空继承了两个类,就可以执行两个类中的方法。
# 但是:当两个父类中出现了重名方法的时候,怎么办?
# 使用MRO算法类解决,但是在经典类和新式类中MRO算法是不同的;

经典类的多继承

深度优先遍历:从头开始,从左向右,一条路到头,然后回头走另一条路到头;

新式类的多继承

MRO计算公式(MRO是一个有序列表,在类被创建时就计算出来的)

# 通用计算公式:
mro(Child(Base1, Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2])
# 其中 Child继承自Base1,Base2
# 如果继承至一个基类:class B(A):	这时B的mro序列为:
'''
mro( B ) = mro( B(A) )
         = [ B ] + merge( mro(A) + [ A ] )
         = [ B ] + merge( [ A ] + [ A ] )
         = [ B, A ]
'''

# 如果继承至多个基类: class B(A1, A2, A3 ...)
'''
mro(B) = mro( B( A1, A2, A3 ...) )
       = [ B ] + merge( mro(A1), mro(A2), mro(A3) ..., [ A1, A2, A3 ] )
# 计算结果为列表,列表中至少一个元素为自己
'''

表头和表尾

表头:
	列表的第一个元素;
表尾:
	列表中表头意外的元素集合(可以为空);

实例:
列表:[A,B,C]
表头是A, 表尾是B和C

merge操作实例:

如计算merge([E,O], [C,E,F,O], [C])
有三个列表:  1         2       3
1.merge不为空,取出第一个列表的表头E,进行判断;
	各个列表的表尾分别是[O],[E,F,O],E在这些表尾的集合中,所以跳过当前列表;
2.取出列表2的表头C,进行判断;
	C不在各个表尾的集合中,因而将C拿出到merge外,并从所有表头删除
	merge([E,O], [C,E,F,O], [C]) = [C] + merge([E,O], [E,F,O])
3.进行下一次新的merge操作......

4.3-继承

计算mro(A)方式

mro(A) = mro(A(B,C))

原式 = [A] + merge(mro(B),mro(C), [B,C])

mro(B) = mro(B(D,E))
	   = [B] + merge(mro(D), mro(E), [D,E])		# 多继承
	   = [B] + merge([D,O], [E,O], [D,E])	# 当继承mro(D(O)) = [D,O]
	   = [B,D] + merge([O], [E,O], [E])	# 拿出并删除D
	   = [B,D,E] + merge([O], [O])
	   = [B,D,E,O]
mro(C) = mro(C(E,F))
	   = [C] + merge(mro(E), mro(F), [E,F])
	   = [C] + merge([E,O], [F,O], [E,F])
	   = [C,E] + merge([O], [F,O], [F])
	   = [C,E,F] + merge([O], [O])
	   = [C,E,F,O]
原式 = [A] + merge(mro(B),mro(C), [B,C])
	= [A] + merge([B,D,E,O], [C,E,F,O], [B,C])
	= [A,B] + merge([D,E,O], [C,E,F,O], [C])
	= [A,B,D] +merge([E,O], [C,E,F,O], [C])
	= [A,B,D,C]  + merge([E,O], [E,F,O])
	= [A,B,D,C,E] + merge([O], [F,O])
	= [A,B,D,C,E,F] + merge([O], [O])
	= [A,B,D,C,E,F,O]

super()深入了解

super是严格按照类的继承顺序执行;

# super可以下一类的其他方法;
class A:
    def f1(self):
        print('in A f1')

    def f2(self):
        print('in A f2')

class Foo(A):
    def f1(self):
        super().f2()
        print('in A Foo')

obj = Foo()
obj.f1()
# in A f2
# in A Foo
# super()严格按照类的mro顺序执行;
class A:
    def f1(self):
        print('in A f1')

class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')

class Bar(A):
    def f1(self):
        print('in Bar')

class Info(Foo, Bar):
    def f1(self):
        super().f1()
        print('in Info f1')
obj = Info()
obj.f1()
# in Bar
# in Foo
# in Info f1
print(Info.mro())
# [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]
# 练习
class A:
    def f1(self):
        print('in A f1')

class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')

class Bar(A):
    def f1(self):
        print('in Bar')

class Info(Foo, Bar):
    def f1(self):
        super(Foo, self).f1()
        print('in Info f1')
obj = Info()
obj.f1()
# in Bar
# in Info f1

print(Info.mro())
# [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]

封装

封装:将内容封装到某个地方,以后再去调用被封装在某处的内容;

封装的使用:

  1. 将内容封装到某处;
  2. 从某处调用被封装的内容;

将内容封装到某处

# 创建类
class Foo:
    def __init__(self, name, age):         # 构建方法,根据类创建对象时自动执行
        self.name = name
        self.age = age

# 根据类Foo创建对象
# 自动执行Foo类的__init__方法
obj1 = Foo('黑色利穆', 25)      # 将黑色利穆和25分别封装到obj1(self)的name和age属性中
obj2 = Foo('白色利穆', 22)      # 将白色利穆和22分别封装到obj2(self)的name和age属性中

# self是一个形式参数,当执行obj1 = Foo('黑色利穆', 25)时,self等于obj1;
#                     当执行obj2 = Foo('白色利穆', 22)时,self等于obj2;
# 所以,内容其实被封装到了对象obj1和obj2中,每个对象都有name和age属性;

从某处调用被封装的内容

调用被封装的内容时,有种情况:

  1. 通过对象直接调用;
  2. 通过self间接调用
# 创建类
class Foo:
    def __init__(self, name, age):         # 构建方法,根据类创建对象时自动执行
        self.name = name
        self.age = age

    def detail(self):
        print(self.name)
        print(self.age)

# 通过对象直接调用被封装的内容
#  对象.属性名
obj1 = Foo('黑色利穆', 25)
print(obj1.name, obj1.age)      # 黑色利穆 25
obj2 = Foo('白色利穆', 22)
print(obj2.name, obj2.age)      # 白色利穆 22


# 用过self间接调用被封装的内容
obj1.detail()
'''
python默认会将obj1传给self参数,即:obj1.detail(obj1);
所以此时内部的self = obj1,即self.name 是黑色利穆;self.age是25
'''
# 黑色利穆
# 25
obj2.detail()

对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或self间接获取到被封装的内容;

多态

多态,同一个对象,多种形态。python默认支持多态;

# 在java或者c定义变量或者给函数传值必须定义数据类型,否则报错;
def func(int a):
    print('a必须是数字')

# python这种弱定义类语言,a可以是任意形态(str,int,object等);
def func(a):
	print('a是什么类型都可以')

鸭子类型

# 你看起来像鸭子,那么你就是鸭子
class A:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
class B:
    def f1(self):
        print('in B f1')
    def f2(self):
        print('in B f2')

obj1 = A()
obj1.f1()
obj1.f2()

obj2 = B()
obj2.f1()
obj2.f2()

# A 和 B 两个类完全没有耦合性,但是却统一了一个标准;
# 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互称为鸭子类型;

# 举例:str tuple list 都有index方法,这就是统一了规范;

类的约束

对类的约束

# 现在要写一个支付功能
class QQpay:
    def pay(self, money):
        print(f'使用QQ支付{money}元')

class Alipay:
    def pay(self, money):
        print(f'使用支付宝支付{money}元')

a = QQpay()
a.pay(100)

b = Alipay()
b.pay(200)

# 这样付款不方便,不合理,需要统一一下付款方式
class QQpay:
    def pay(self, money):
        print(f'使用QQ支付{money}元')

class Alipay:
    def pay(self, money):
        print(f'使用支付宝支付{money}元')

def pay(obj, money):        # 这个函数就是统一支付规则:归一化设计
    obj.pay(money)

c = QQpay()
d = Alipay()

pay(c, 110)
pay(d, 120)

# 换人,写了个微信支付的功能
class QQpay:
    def pay(self, money):
        print(f'使用QQ支付{money}元')

class Alipay:
    def pay(self, money):
        print(f'使用支付宝支付{money}元')

class Wechatpay:
    def fuqian(self, money):
        print(f'使用微信支付{money}元')

def pay(obj, money):        # 这个函数就是统一支付规则:归一化设计
    obj.pay(money)

c = QQpay()
d = Alipay()

pay(c, 110)
pay(d, 120)

e = Wechatpay()
e.fuqian(130)

对类的约束:两种:

  1. 提取父类,然后在父类中定义好方法,这个方法中什么都不用干,抛出一个异常就可以,这样所有的子类都必须重写这个方法,否则,访问的时候就会报错;
  2. 使用元类来描述父类,在元类中给出一个抽象的方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果;
# 代码不规范,所以现在开始制定规范了
# 提取父类方法
class Payment():
    '''
    此类,什么都不做,就是一个标准,按照我定义的方法去定义自己的方法
    '''
    def pay(self, money):
        raise Exception('你没有实现pay方法')

class QQpay(Payment):
    def pay(self, money):
        print(f'使用QQ支付{money}元')

class Alipay(Payment):
    def pay(self, money):
        print(f'使用支付宝支付{money}元')

class Wechatpay(Payment):
    def fuqian(self, money):
        print(f'使用微信支付{money}元')

def pay(obj, money):        # 这个函数就是统一支付规则:归一化设计
    obj.pay(money)

c = QQpay()
d = Alipay()
e = Wechatpay()

pay(c, 110)
pay(d, 120)
pay(e, 130)     # Exception: 你没有实现pay方法

# 代码不规范,所以现在开始制定规范了
# 引入抽象类的概念处理
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):       # 抽象类,接口类,规范和约束,metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass

class QQpay(Payment):
    def pay(self, money):
        print(f'使用QQ支付{money}元')

class Alipay(Payment):
    def pay(self, money):
        print(f'使用支付宝支付{money}元')

class Wechatpay(Payment):
    def fuqian(self, money):
        print(f'使用微信支付{money}元')

def pay(obj, money):        # 这个函数就是统一支付规则:归一化设计
    obj.pay(money)

c = QQpay()
d = Alipay()
e = Wechatpay()

pay(c, 110)
pay(d, 120)
pay(e, 130)     # TypeError: Can't instantiate abstract class Wechatpay with abstract methods pay

# 抽象类和接口类做的事情:建立规范
# 制定一个类的metaclass是ABCMeta;
# 这个类就变成了抽象类(接口类);

约束其实就是父类对子类进行约束,子类必须要写父类定义的那个方法。在python中约束的方式和方法有两种:

  1. 使用抽象类和抽象方法,该方案来源于java和c,使用频率比较少;
  2. 使用人为抛出异常的方案,尽量抛出NotImplementedError。错误比较明确(推荐);
posted @ 2020-12-07 18:39  黑色利穆  阅读(111)  评论(0编辑  收藏  举报