4.3-继承-封装-多态
目录
继承-封装-多态
python面向对象的三大特征:继承,封装,多态
- 封装:把很多数据封装到一个对象中,把固定功能的代码封装到一个代码块,函数,对象,打包成模块。这都属于封装的思想。具体的情况具体分析:比如,写了一个函数,这个也可以成为封装;在面向对象的思想中,是把一些看似无关紧要的内容组合到一起同一进行存储和使用,就是封装;
- 继承:子类可以自动拥有父类中除了私有属性外的其他所有内容;必须现有基类,后有子类。在python中实现继承非常简单:在声明类的时候,在类名后面添加一个小括号,就可以完成继承关系;使用继承的情况:
- 从代码层面看:两个类具有相同的功能或者特征的时候,可以采用继承的形式,提取一个父类,这个父类中编写着两个类相同的部分,然后两个类分别去继承这个类就可以了(好处,可以避免写很多重复的功能和代码)。
- 从语义去分析:如果语境中出现了x是一种y,这时,y是一种泛化的概念。x比y更具体,这时x就是y的子类。比如:猫是一种动物,猫继承动物。动物能动,猫也能动,这时猫在创建的时候就有了动物的动的属性。
- 多态:同一个对象,多种形态。比如:创建一个变量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中类的种类(继承):
-
python2中有两种:
- 经典类(2.2之前(基类的根什么都不写))
- 新式类(2.2之后,基类的根是object)
-
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操作......
计算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'>]
封装
封装:将内容封装到某个地方,以后再去调用被封装在某处的内容;
封装的使用:
- 将内容封装到某处;
- 从某处调用被封装的内容;
将内容封装到某处
# 创建类
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属性;
从某处调用被封装的内容
调用被封装的内容时,有种情况:
- 通过对象直接调用;
- 通过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)
对类的约束:两种:
- 提取父类,然后在父类中定义好方法,这个方法中什么都不用干,抛出一个异常就可以,这样所有的子类都必须重写这个方法,否则,访问的时候就会报错;
- 使用元类来描述父类,在元类中给出一个抽象的方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果;
# 代码不规范,所以现在开始制定规范了
# 提取父类方法
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中约束的方式和方法有两种:
- 使用抽象类和抽象方法,该方案来源于java和c,使用频率比较少;
- 使用人为抛出异常的方案,尽量抛出NotImplementedError。错误比较明确(推荐);