面向对象的语言的特征
- 继承
- 多态
- 封装
继承 inheritance
继承 inheritance / 派生 derived
什么继承/派生
继承是指从已有的类中派生出新的类,新类具有原类的行为,并能扩展新的行为
派生类就是从一个已有类中衍生成新类,在新类上可以添加新的属性和行为
作用:
1.用继承派生机制,可以将一些共有功能加在基类中,实现代码的共享
2.在不改变基类的代码的基础上改变原有的功能名语:
基类(base class) /超类(super class)/ 父类(father class)
派生类(derived class) / 子类(child class)
继承有两种用途:
- 继承基类的方法,并且做出自己的改变或者扩展(代码重用)
- 声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
单继承:
语法:
class 类名(基类名):
语句块
说明:
单继承是指由一个基类衍生出新的类继承说明:
python3 任何类都直接或间接的继承自object类
object 类是一切类的超类类的__base__属性
__base__属性用来记录此类的基类
覆盖
覆盖是指在有继承关系的类中,子类中实现了与基类同名的方法,在子类的实例调用该方法时,实际调用的是子类中的覆盖版本,这种现象叫做覆盖
子类对象显式调用基类(被覆盖)方法的方式:
基类名.方法名(实例, 实际调用传参)
super 函数
super(cls, obj) 返回绑定超类的实例(要求obj必须是cls类型的实例)
super() 返回绑定超类的实例,等同于: super(__class__, 实例方法的第一个参数), 必须在方法内调用作用:
借助super() 返回的实例间接调用其父类的覆盖方法示例:
# 此示例示意用super函数返回的对象调用父类的覆盖方法 class A: def works(self): print("A.works被调用") class B(A): ''' B类继承自A类''' def works(self): print("B.works被调用") def super_work(self): self.works() # B.works被调用 super(B, self).works() # A.works被调用 super().works() # A.works被调用 b = B() # b.works() # B.works被调用 # super(B, b).works() # A.works被调用 b.super_work() # ... # super().works() # 出错,只能在方法内调用
显式调用基类的初始化方法:
当子类中实现了 __init__ 方法,基类的构造方法并不会被调用
def __init__(self, ...)示例:
# 此示例示意 用super函数显示调用基类__init__初始化方法 class Human: def __init__(self, n, a): self.name, self.age = n, a print("Human的__init__方法被调用") def infos(self): print("姓名:", self.name) print("年龄:", self.age) class Student(Human): def __init__(self, n, a, s=0): super().__init__(n, a) # 显式调用父类的初始化方法 self.score = s # 添加成绩属性 print("Student类的__init__方法被调用") def infos(self): super().infos() # 调用父类的方法 print("成绩:", self.score) s1 = Student('小张', 20, 100) s1.infos()
子类调用父类
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一:父类名.父类方法
1 class A: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def test(self): 7 print('test function') 8 9 class B(A): #新建类B继承类A,类A中的属性全部被类B继承 10 def __init__(self, name, age, country): 11 A.__init__(self, name, age) #引用父类的属性 12 self.country = country #增加自身独有的属性 13 14 def test(self): 15 print('test function B') 16 pass 17 18 b1 = B('jack', 21, 'China') #类B的实例可引用父类B的属性,如果有重名属性,以自身类的属性优先 19 print(b1.name) 20 print(b1.age) 21 print(b1.country) 22 b1.test() 23 24 #运行结果 25 #jack 26 #21 27 #China 28 #test function B
方法二:super()函数
1 #super的用法:只能在新式类中使用 2 #python3中: 3 class People: 4 def __init__(self, name, sex, age): 5 self.name = name 6 self.sex = sex 7 self.age = age 8 9 def walk(self): 10 print('%s is walking' %self.name) 11 12 class Chinese(People): 13 country = 'China' 14 def __init__(self, name, sex, age, language = 'Chinese'): 15 super().__init__(name, sex, age) #super(). 16 self.language = language 17 def walk(self,x): 18 super().walk() #super(). 19 print('subclass: %s' % x) 20 21 c = Chinese('egon', 'male', 18) 22 print(c.name, c.sex, c.age, c.language) 23 c.walk('walk')
多继承 multiple inheritance
多继承是指一个子类继承自两个或两个以上的基类
语法:
class 类名(基类名1, 基类名2, ...):
...
说明:
1. 一个子类同时继承自多个父类,父类中的方法可以同时被继承下来
2. 如果两个父类中有同名的方法,而在子类中又没有覆盖此方法时,调用结果难以确定
python的类如果继承了多个类,那么其寻找方法的方式有两种:深度优先和广度优先(python3中统一都为新式类)
- 当类是经典类时,多继承情况下,深度优先
- 当类是新式类时,多继承情况下,广度优先
多继承的问题(缺陷):
标识符(名字空间)冲突的问题(在名字空间冲突时调用前面的基类)
要谨慎使用继承示例:
# 此示例示意多继承名字冲突问题 # 小张写了一个类A class A: def m(self): print('A.m()被调用') # 小李写了一个类B class B: def m(self): print('B.m()被调用') # 小王感觉小张和小李写的两个类自己可以用 class AB(A, B): pass ab = AB() ab.m() # A.m()被调用
多继承的 MRO (Method Resolution Order) 问题
类的 __mro__ 属性
此属性用来记录类的方法查找顺序1 class A(object): 2 def test(self): 3 print('from A') 4 5 class B(A): 6 def test(self): 7 print('from B') 8 9 class C(A): 10 def test(self): 11 print('from C') 12 13 class D(B): 14 def test(self): 15 print('from D') 16 17 class E(C): 18 def test(self): 19 print('from E') 20 21 class F(D,E): 22 # def test(self): 23 # print('from F') 24 pass 25 f1=F() 26 f1.test() 27 print(F.__mro__) #只有新式类才有这个属性可以查看线性列表,经典类没有这个属性 28 29 #新式类继承顺序:F->D->B->E->C->A 30 #经典类继承顺序:F->D->B->A->E->C 31 #python3中统一都是新式类
以下摘自 https://www.cnblogs.com/OldJack/p/6734493.html
一、继承
继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类。
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass1 pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
使用'__bases__'方法查看继承
>>> SubClass1.__bases__ (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
二、继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。是一种归类的方法。
抽象分成两个层次:
1. 将奥巴马和梅西这俩对象比较像的部分抽取成类;
2. 将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类。
三、 继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
class A: def test(self): print('test function') class B(A): #新建类B继承类A,类A中的属性全部被类B继承 pass b1 = B() #类B的实例可引用父类B的'test'方法 b1.test() #运行结果 #test function
用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置,大大减少了编程工作量,这就是常说的软件重用。不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就大大缩短了软件开发周期,对大型软件开发来说,意义重大。
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class A: def test(self): print('test function') class B(A): #新建类B继承类A,类A中的属性全部被类B继承 def test(self): print('test function B') pass b1 = B() #类B的实例可引用父类B的'test'方法,但自身类下也有重名的’test‘方法,以自身优先 b1.test() #运行结果 #test function B
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
class A: def __init__(self, name, age): self.name = name self.age = age def test(self): print('test function') class B(A): #新建类B继承类A,类A中的属性全部被类B继承 pass b1 = B('jack', 21) #类B的实例可引用父类B的'test'方法 print(b1.name) print(b1.age) b1.test() #运行结果 #jack #21 #test function
class A: def __init__(self, name, age): self.name = name self.age = age def test(self): print('test function') class B(A): #新建类B继承类A,类A中的属性全部被类B继承 def __init__(self, name, age, country): A.__init__(self, name, age) #引用父类的属性 self.country = country #增加自身独有的属性 def test(self): print('test function B') pass b1 = B('jack', 21, 'China') #类B的实例可引用父类B的属性,如果有重名属性,以自身类的属性优先 print(b1.name) print(b1.age) print(b1.country) b1.test() #运行结果 #jack #21 #China #test function B
四、组合与重用性
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
class Teacher: def __init__(self, name, gender, course): self.name = name self.gender = gender self.course = course class Course: def __init__(self, name, price, period): self.name = name self.price = price self.period = period course_obj = Course('Python', 15800, '5months') #新建课程对象 #老师与课程 t_c = Teacher('egon', 'male', course_obj) #新建老师实例,组合课程对象 print(t_c.course.name) #打印该老师所授的课程名 #运行结果 #Python
组合与继承都是有效地利用已有类的资源的重要方式,但是二者的概念和使用场景皆不同。
1. 继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching
2. 组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
class BirthDate: def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Teacher: def __init__(self,name,gender): self.name=name self.gender=gender def teach(self): print('teaching') class Professor(Teacher): def __init__(self,name,gender,birth,course): Teacher.__init__(self,name,gender) self.birth=birth self.course=course p1=Professor('egon','male', BirthDate('1995','1','27'), Couse('python','28000','4 months')) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) #运行结果: #1 27 #python 28000 4 months
组合实例:
1 #组合重用代码 2 class Teacher: 3 def __init__(self, name, sex, args): 4 self.name = name 5 self.sex = sex 6 self.args = args 7 8 class Student: 9 def __init__(self, name, sex, args): 10 self.name = name 11 self.sex = sex 12 self.args = args 13 14 class Course: 15 def __init__(self, name, price, period): 16 self.name = name 17 self.price = price 18 self.period = period 19 20 class Birth: 21 def __init__(self, year, month, day): 22 self.year = year 23 self.month = month 24 self.day = day 25 26 class Score: 27 def __init__(self, score): 28 self.score = score 29 30 def score_grade(self): 31 if self.score > 90: 32 g = 'A' 33 elif self.score > 80: 34 g = 'B' 35 elif self.score > 70: 36 g = 'C' 37 elif self.score > 60: 38 g = 'D' 39 else: 40 g = 'F' 41 return g 42 course_obj = Course('Python', 15800, '5months') #课程 43 birth_obj_t = Birth(2000, 4, 19) #老师生日 44 birth_obj_s = Birth(2009, 9, 21) #学生生日 45 score_obj = Score(91) #学生成绩 46 #老师与课程 47 t_c = Teacher('egon', 'male', course_obj) 48 print('%s老师教授%s' % (t_c.name, t_c.args.name)) #打印该老师所授的课程名 49 #学生与课程 50 s_c = Student('jack', 'male', course_obj) 51 print('%s学习%s' % (s_c.name, s_c.args.name)) 52 #老师与生日 53 t_b = Teacher('egon', 'male', birth_obj_t) 54 print('%s老师生日为:%s年 %s月 %s日'%(t_b.name, t_b.args.year, t_b.args.month, t_b.args.day)) 55 #学生与生日 56 s_b = Student('jack', 'male', birth_obj_s) 57 print('%s学生生日为:%s年 %s月 %s日'%(s_b.name, s_b.args.year, s_b.args.month, s_b.args.day)) 58 #学生和分数 59 s_s = Student('jack', 'male', score_obj) 60 print('%s学生的成绩为%s,等级为%s' % (s_s.name, s_s.args.score, s_s.args.score_grade())) 61 62 63 #运行结果: 64 #egon老师教授Python 65 #jack学习Python 66 #egon老师生日为:2000年 4月 19日 67 #jack学生生日为:2009年 9月 21日 68 #jack学生的成绩为91,等级为A
封装 enclosure
封装是指隐藏类的实现细节,让使用者不关心这些细节
封装的目的是让使用者通过尽可能少的方法(或属性)操作对象封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变
私有属性和方法
python类中以双下划线('__') 开头,不以双下划线结尾的标识符为私有成员,私有成员或只能用类内的方法进行访问和修改
以__开头的实例变量有私有属性
以__开头的方法为私有方法示例:
# 此示例示意私有属性和私有方法 class A: def __init__(self): self.__p1 = 100 # 私有属性 def show_A(self): print('self.__p1: ', self.__p1) self.__m1() # 调用自己的方法 def __m1(self): # 私有方法 print("__m1(self)方法被调用") a = A() a.show_A() # a.__p1: 100 # print(a.__p1) # 出错,在类外部不能访问a的私有属性__p1 # a.__m1() # 出错,不能调用私有方法 class B(A): pass b = B() # print(b.__p1) # 出错, 子类对象不能访问父类中的私有成员 # b.__m1() # 出错
单下划线、双下划线、头尾双下划线说明:
__foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。
_foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
__foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
多态 polymorphic
多态:
字面意思: 多种状态
多态是指在有继承/派生关系的类中,调用基类对象的方法,实际能调用子类的覆盖方法的现象叫多态状态:
静态(编译时状态)
动态(运行时状态)
说明:
多态调用方法与对象相关,不与类相关
Python的全部对象都只有"运行时状态(动态)", 没有"C++语言"里的"编译时状态(静态)"
# 此示例示意python中的运行时状态
class Shape:
def draw(self):
print('Shape的draw方法被调用')
class Point(Shape):
def draw(self):
print("正在画一个点")
class Circle(Shape):
def draw(self):
super().draw()
print("正在画一个圆")
def my_draw(s):
s.draw() #此处显示出'动态'
s1 = Circle()
s2 = Point()
my_draw(s2)
my_draw(s1)
多态与多态性
1.多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)。——类的定义阶段
- 序列类型有多种形态:字符串,列表,元组。
- 动物有多种形态:人,狗,猪
- 文件有多种形态:文本文件,可执行文件
2. 多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。——类的调用阶段
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
3. 多态性的好处
- 增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
- 增加了程序的可扩展性,通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用