文章目录
面向对象的三大特性
面向对象的三大特征分别为:
封装、继承、多态
一、面向对象三大特性之封装
封装指的就是把数据与功能都整合到一起
,听起来是不是很熟悉。
没错,我们之前所说的‘整合’二字其实就是封装的通俗说法
什么是封装
在程序设计中,封装(Encapsulation)是对具体对象的一种抽象,即将某些部分隐藏起来,在程序外部看不到,其含义是其他程序无法调用。
要了解封装,就离不开’私有化’,就是将类或者是函数中的某些属性限制在某一个区域之内,外部无法调用。
为什么要封装
封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)
二、面向对象三大特性之继承
三大特性中继承最为核心(实操最多)
1.什么是继承?
什么是继承?
继承就是一种新建类的方式,新建出来的类我们称为是'子类或者叫派生类',
被继承的类我们称为是’父类或者叫基类‘
# 子类可以继承父类的所有属性和方法!
通俗的说:
在现实生活中继承表示人与人之间资源的从属关系
eg:在生活中儿子继承了父亲就拥有了父亲所有资源的支配权限
在编程世界中继承表示类与类之间资源的从属关系
eg:在编程时间中类A继承类B就拥有了类B的所有的数据和方法使用权限
2.为什么要用继承?
类解决了什么问题:对象与对象之间的代码冗余问题
继承解决了什么问题:类与类之间的代码冗余问题
3.怎么样继承?
怎么样继承?
经典类:没有继承object类的子子孙孙类都是经典类(深度优化算法)
格式写法:
class 类名:
类体代码
新式类:继承了object类的子子孙孙类的都是新式类(广度优先算法)
格式写法:
class 类名(object):
类体代码
'在python3中类名后面小括号中不写object也是会继承一个object类'
'在有python2中才会区分经典类和新式类,在python3中只有新式类'
也就是说python3中默认的类都是继承了object类,
所以在python3中就没有了经典类和新式类的说法了
=========================================================
'''
继承中有单继承和多继承
单继承:一个类只继承了一个类
格式:
class 类名(A):
类体代码
# 单继承就是括号内填写一个类名
多继承:一个类继承两个或两个以上的类
格式:
class 类名(A,B):
类体代码
# 多继承就是括号内填写多个类名,彼此用逗号隔开
'''
class Parent1: # 经典类写法
pass
class Parent2(object): # 新式类写法
pass
'''Sub1类继承了Parent1的类,所以Sub1就称为子类,Parent1就称为父类'''
class Sub1(Parent1):
pass
'''多继承的类:Sub2就是子类,Parent1和Parent2都是父类'''
class Sub2(Parent1,Parent2):
pass
'查看继承的个数的方法'
print(Sub2.__bases__) # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
print(Sub1.__bases__) # (<class '__main__.Parent1'>,)
3.1简单单继承实操
'''简单单继承实操'''
class A:
name = 'chen'
def game(self):
print('game game game')
@classmethod # 使类直接调用,无需传参
def game1(cls):
print('game game')
class B(A):
pass
'1.通过子类直接调用用父类中的数据和方法'
print(B.name) # chen
print(B.game) # <function A.game at 0x0000022C9307A1F0>
'因为默认是对象方法,所以用类直接调用需要传参'
B.game(1) # game game game
'所以可以用装饰器classmethod直接用子类调用父类中的对象方法,无需传参'
B.game1() # game game
print('===============')
# 2.通过子类实例化对象调用父类中的数据和方法
obj = B()
print(obj.name) # chen
obj.game() # game game game
obj.game1() # game game
3.2简单多继承实操
'''简单多继承实操'''
class A1:
print('from A1')
name1 = 'a1'
class A2:
print('from A2')
name2 = 'a2'
class A3:
print('from A3')
name3 = 'a3'
class B(A1, A2, A3):
pass
B()
print(B.name1) # a1
print(B.name2) # a2
print(B.name3) # a3
4.继承的本质
继承的本质:
本质1:对象:数据和功能的结合体
类(子类):多个对象相同数据和功能的结合体
父类:多个类(子类)相同数据和功能的结合体
>>>>:类与父类本质都是为了节省代码
本质2:继承的本质一个分为两部分
抽象:将多个类相同的东西抽出去形成一个新的类
继承:将多个类继承刚刚抽出去的新的类
'''第一步:定义两个独立的类'''
class Student():
school = 'HF'
def __init__(self,name,age,gender):
self.name =name
self.age = age
self.gender = gender
def choose_course(self):
print(f"{self.name}正在选课")
class Teacher():
school = 'HF'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def choose_course(self):
print(f"{self.name}正在授课")
stu = Student('jack', 18, 'male')
teacher1 = Teacher('chen', 25, 'male')
print(stu.__dict__) # {'name': 'jack', 'age': 18, 'gender': 'male'}
print(stu.name) # jack
stu.choose_course() # jack正在选课
print(teacher1.__dict__) # {'name': 'chen', 'age': 25, 'gender': 'male'}
print(teacher1.name) # chen
teacher1.choose_course() # chen正在授课
'''
从上可以看出,两个类中有很多相同的数据,会显得代码很冗余
我们可以用抽象的方式把两个类中
相同的数据抽出去形成一个新的类,然后将新类分别给这两个类继承,
这样就可以解决类与类之间代码冗余的问题
'''
'''第二步:用继承的方式把类冗余的代码抽到新的类中'''
'''
可以把学生类和老师类中的相同的代码,放到一个新类中,让新类作它们的父类
'''
class People():
school = 'HF'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Student(People): # 学生类
def __init__(self, name, age, gender,course=None):
# 因为把子类中冗余的方法数据放到父类中了,可以通过指名道姓的方法调用
# People.__init__(self,name,age,gender) # 指名道姓的方式
'''知名道姓的调用方式,不依赖于继承'''
'''
另一种就是依赖于继承
通过super关键字和mro列表
'''
# super(Student, self).__init__(self, name, age, gender) # 这种是python2中的写法
super().__init__(name, age, gender) # 这种是python3的写法
'这种方式依赖于继承,如果去掉继承的父类就会报错'
if course is None:
self.course = []
self.course = course
def choose_course(self):
print(f"{self.name}正在选课{self.course}")
class Teacher(People): # 老师类
def __init__(self, name, age, gender, level):
# 通过知名道姓的方式直接调用
# (因为是类调用绑定对象的方法所以得对应的传递参数)
# People.__init__(self, name, age, gender) # 指名道姓的方式
'''另一种依靠继承的方式'''
# super(Teacher, self).__init__(self, name, age, gender) # 这种是python2中的写法
super().__init__(name, age, gender) # 这是python3中的写法
self.level = level
def choose_course(self):
print(f"{self.name}正在授课")
# 可以通过子类取出父类的数据和方法
print(Student.school) # HF
print(Teacher.school) # HF
stu = Student('jack', 18, 'male', 'python')
teacher1 = Teacher('chen', 25, 'male', 10)
print(stu.__dict__) #{'name': 'jack', 'age': 18, 'gender': 'male', 'course': 'python'}
print(teacher1.__dict__) #{'name': 'chen', 'age': 25, 'gender': 'male', 'level': 10}
print(stu.name) # jack
print(teacher1.name) # chen
stu.choose_course() # jack正在选课python
teacher1.choose_course() # chen正在授课
'''通过类的方法mro()或者类的属性__mro__可以输出这个类的继承层次结构'''
print(Student.mro())
print(Teacher.mro())
4.1、super和mro的使用
'''super和mro的使用'''
class A:
__name = 'hello'
def test(self):
super().test()
class B:
name = 'world'
def test(self):
print('from B')
class C(A,B):
pass
c = C()
c.test() # from B
'''
mro列表是通过一个C3算法得出来的,我们无需明白底层原理,只需要知道每一个类的mro列表到底是什么,
然后按照这个列表去查找就行了
'''
'''在打印mro列表的时候,一定是从起始类开始'''
print(C.mro()) # 返回的列表形式
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
print(C.__mro__) # 返回的元组形式
'''每个类都会有自己的mro列表'''
print(A.mro()) #[<class '__main__.A'>, <class 'object'>]
'''如果以后继承类中出现了super关键字,不清楚顺序的,可以先用mro列表打印起始类的所有的'''
5.单继承下的属性查找
'''单继承与多继承下的属性查找'''
class Foo():
def f1(self):
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
class Bar(Foo):
def f1(self):
print('from Bar.f1')
'''
单继承下的属性查找:先从对象自己名称空间中查找,如果没找到则到产生这个对象的类中查找
如果还没有,才会去继承的父类中查找
>>>>::对象本身->产生对象的类->继承的父类
'''
b = Bar()
b.f1() # from Bar.f1
b.f2() # from Foo.f2
'''父类如果不想让子类覆盖自己的方法,可以采用双下滑线开头的方式设置为私有化'''
class Foo():
def __f1(self): # 变形为_Foo__f1
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
self.__f1() # 变形为self._Foo__f1.因而只会调用自己所在的类中的方法
class Bar(Foo):
def __f1(self):
print('from Bar.f1')
b = Bar()
# print(Bar.__dict__)
# print(Bar.mro())
b.f2()
6.多继承下的属性查找
大多数面向对象语言都不支持多继承
,而在Python中,一个子类可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的Diamond Problem菱形问题(或称钻石问题,有时候也被称为‘死亡钻石’),菱形起始就是对下面这种继承结构的形象比喻
'''多继承下的属性查找'''
'''
多继承的查找,涉及到菱形查找和非菱形查找
非菱形查找:就是一条一条分支的查找
# 可以通过mro列表查看到多继承的查找顺序
菱形查找:
新式类 广度优先查找
经典类 深度优先查找
# 经典类是在python2中的,所以多继承下的属性查找,如果按照属性找不到就按照深度优先
# 在python3当中都是新式类,所以多继承下的属性查找,如果按照属性找不到就按照广度优先
'''
6.1非菱形查找
多继承结构为非菱形结构,此时,会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
'''非菱形查找'''
class E:
# def test(self):
# print('from E')
pass
class F:
# def test(self):
# print('from F')
pass
class B(E):
# def test(self):
# print('from B')
pass
class C(F):
# def test(self):
# print('from C')
pass
class D:
def test(self):
print('from D')
class A(B, C, D):
# def test(self):
# print('from A')
pass
obj=A()
'''非菱形查找从对象的名称空间中开始,没有就往产生对象的类中查找,没有就再往父类中查找,最后在object中查找'''
'''如果不知道怎么查找,可以直接用mro关键字,从起始类,就可以看到起始类的查找顺序'''
print(A.mro())
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>,
# <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
obj.test()
6.2菱形查找
如果继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先
当类是经典类时,在多继承情况下,在要查找的属性不存在时,会按照深度优先的方式查找下去
'''经典类:深度查找'''
class G: # 在python2中,未继承object的类及其子类,都是经典类
def test(self):
print('from G')
class E(G):
def test(self):
print('from E')
class F(G):
def test(self):
print('from F')
class B(E):
def test(self):
print('from B')
class C(F):
def test(self):
print('from C')
class D(G):
def test(self):
print('from D')
class A(B,C,D):
# def test(self):
# print('from A')
pass
obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试
当类是新式类时,多继承情况下,在要查找属性不存在时,会按照广度优先的方式查找下去
'''新式类:广度查找'''
class G(object):
def test(self):
print('from G')
class E(G):
def test(self):
print('from E')
class F(G):
def test(self):
print('from F')
class B(E):
def test(self):
print('from B')
class C(F):
def test(self):
print('from C')
class D(G):
def test(self):
print('from D')
class A(B,C,D):
# def test(self):
# print('from A')
pass
obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证
7.继承的实现原理
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>,
<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
所以obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性查找的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后再B中找到方法test
1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
三、面向对象三大特性之多态
什么是多态呢?
多态就是一种事物的形态(水:液态、气态、固态)
一种事物有多中形态 但是相同的功能应该有相同的名字
"""之所以说人、狗、猪是动物,是因为他们都具备动物的特征,speak功能"""
"""多态带来的特性:在不考虑对象类型的情况下,直接调用对象的方法或者属性"""
import abc # abstract class 抽象类 具体的Specific
class Animal(metaclass=abc.ABCMeta): # 把animal类变成了抽象类
"""父类中得方法不是为了实现逻辑的,实现功能的,而是单纯的为了限制子类的行为"""
@abc.abstractmethod # 把抽象类中得方法变成抽象方法, 它不实现具体的功能,就是单纯的为了限制子类中的方法
def speak(self):
pass
@abc.abstractmethod
def jiao(self):
pass
"""抽象类和普通类有什么区别? 抽象类只能够被继承、不能够被实例化"""
# Animal() # Can't instantiate abstract class Animal with abstract methods speak
"""怎么限制子类People类必须有speak功能? 我们可以在父类中来限制子类的行为,其实就是限制子类中必须有某些方法"""
"""Python崇尚的是鸭子类型"""
"""鸭子类型就是更多的关注的是对象的行为,而不是对象的类型"""
class People(Animal):
def speak(self):pass
# print('from People.speak')
def jiao(self):
pass
class Dog(Animal):
def speak(self):
pass
class Pig(Animal):
def speak(self):
pass
def animal(obj):
return obj.speak()
animal(obj)
animal(obj1)
animal(obj2)
"""面试题:请举出Python中使用多态的例子:len"""
len('hello')
len([1,2,3,4])
len((1,2,3,4))
def len(obj):
return obj.__len__
len('helloworld')
len([1,2,3,4])
len((1,2,3,4))