继承、派生、组合
2017-07-03 16:26
330 查看
继承
类之间会有一些相同的属性,提取这些相同的属性做成基类(父类)继承是创建类的一种方式,通过代码重用,减少代码冗余。把父类的属性遗传给子类。
在创建类时,新建的类可以继承一个或多个父类,方式如下:
class ClassName(BaseName1, BaseName2,...): # 括号内是继承的父类 '类注释文档' pass
继承是一种类与类之间的关系: 什么 是 什么 。
查看所有继承:ClassName.__bases__ ,返回元组。
在python3中,所有类默认继承object。
在python2中,继承了object的子类都称为新式类,而没有继承object及其子类的,称为经典类。
继承的子类会获得父类的所有属性。对象在调用属性时,寻找顺序如下:
对象名称空间 >>> 所属类名称空间 >>> 父类名称空间
单纯的继承没有意义,子类需要派生自己新的属性:
class People(): # 默认继承object def __init__(self, name, age, sex): print('initializing...') self.name = name self.age = age self.sex= sex def walk(self): print('%s can walk well' % self.name) class Teacher(People): def __init__(self, name, age, sex, salary, level): People.__init__(self, name, age, sex) # 继承父类的属性 # 派生子类自己的数据属性 self.salary = salary self.level = level # 派生子类自己的函数属性,父类中有的就不用重复写了。 def teach(self): print('%s is good at teaching' % self.name)
组合
组合也是一种类与类之间的关系: 什么 有 什么 。也是通过代码重用,减少代码冗余。class Date(): def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def tell_info(self): print('birth is %s-%s-%s' % (self.year, self.mon, self.day)) class Teacher(): def __init__(self, name, age, *args): # 这里用*args接收year,mon,day self.name = name self.age = age self.birth = Date(*args) # birth属性的是Date类的对象。 t = Teacher('egon',18,1990,2,30) # 1990,2,30 这三个参数传给Date中的__init__函数 t.birth.tell_info() ''' birth is 1990-2-30 '''
class People: def __init__(self,name,age,sex): # 函数的默认参数不要写成可变的,所以couser=[]不要写在形参位置 self.name = name self.age = age self.sex = sex self.course = [] def tell_info(self): print(''' ----- %s info ----- NAME: %s AGE: %s SEX: %s '''%(self.name,self.name, self.age,self.sex)) def tell_course(self): if self.course: for i in self.course: print(i.tell_info()) else: print('no course added!') # def tell_couser(self): # if 'couser' in self.__dict__: # 如果不设couser默认参数,那么就没有couser属性, # 如果用 if self.couser: 就会报错。判断字符串在名称空间的字典就没问题 # for i in self.couser: # print(i.tell_info) # else: # print('no couser added') class Teacher(People): def __init__(self,name,age,sex,salary,level): # 函数的默认参数不要写成可变的,所以couser=[]不要写在形参位置 People.__init__(self,name,age,sex) self.salary = salary self.level = level class Student(People): def __init__(self,name,age,sex,group): People.__init__(self,name,age,sex) self.group=group class Date: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def tell_info(self,obj): pri 4000 nt('%s 出生于:%s-%s-%s' % (obj.name,self.year, self.mon, self.day)) class Course: def __init__(self,name,price,period): self.name = name self.price = price self.period = period def tell_info(self): print(''' ----- %s info ----- name: %s price: %s period: %s '''%(self.name,self.name,self.price,self.period)) Birth = Date(1990,2,31) # 创建时间对象 python = Course('python',15800,'6monts') # 创建课程对象python go = Course('go', 10000, '6monts') # 创建课程对象 go alex = Teacher('alex',84,'female',300,1) # 创建老师对象 alex.birth = Birth # 将日期组合进老师对象 alex.course.append(python) # 将课程对象组合进老师对象 alex.course.append(go) alex.birth.tell_info(alex) alex.tell_course() alex.tell_info() ayhan = Student('ayhan',18,'male','group7') ayhan.tell_course()
接口与归一化设计
接口,隐藏具体的实现细节,将使用简单化。比如,在linux中,一切皆文件,比如文本文件、磁盘文件、进程文件,都有读写操作,使用者不需要关心每种文件的读和写是如何具体实现的,只需要知道只要是文件,就应该有读方法和写方法,调用这两个方法,就可以完成想要的效果。我们可以通过继承,来模拟这种情况:class File(): # 定义文件类来模仿接口的概念 def read(self): # 定义接口函数read pass def write(self): # 定义接口函数write pass class Txt(File): # 具体实现文本文件的read和write def read(self): print('文本文件读方法') # 这里用print来模拟,具体的实现可能是很复杂的。 def write(self): print('文本文件读方法') class Sata(File): # 具体实现磁盘文件的read和write def read(self): print('磁盘文件读方法') def write(self): print('磁盘文件写方法') class Process(File): # 具体实现进程文件的read和write def read(self): print('进程文件读方法') def write(self): print('进程文件写方法') t = Txt() s = Sata() p = Process() # 具体到三个不同的对象,使用者只需要知道它们是文件,是文件就有read和write方法: # t.read() t.read() s.read() s.write() p.read() p.write()
上面这个栗子中,定义的File这个父类,只放方法名(read, write),相当于一个模板,具体的功能由子类来实现。
但是,子类在定义时,并不一定要遵循父类中的方法,因此,父类有必要对此加以限制:
1. 子类必须要有父类的方法
2. 子类实现的方法必须跟父类的方法的名字一样
# 知识准备:抽象类就是基于类抽象而来的。 # 抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法 import abc # 导入abc模块实现抽象类 class File(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass
通过上面这种方式,我们再定义子类时,就必须要有父类的方法,并且方法名一样,否则就无法实例化对象:
class Txt(File): # 父类是File def du(self): print('文本文件读方法') def xie(self): print('文本文件写方法') t = Txt() '''虽然上面的Txt类在定义阶段没问题,但是在实例化创建对象时,会提示无法实例化抽象类, t = Txt() TypeError: Can't instantiate abstract class Txt with abstract methods read, write '''
这种把所有方法都统一起来的方式,就是归一化设计,方便使用。
对象的序列化
pickle可以序列化任何python的数据类型import pickle class Sample: # 定义类 pass obj = Sample() # 创建对象 with open(file,'wb')as f: # 序列化 pickle.dump(obj,f) with open(file, 'rb')as f: # 反序列化 obj = pickle.load(f)
注意,对象是依赖于类的,如果反序列化出的对象没有在内存中找到所属的类,就会报错。解决这个问题,可以通过导入模块的方式,将类导入。因为导入会执行模块的内容,加载到内存。
继承的实现原理MRO
情况一:
属性的查找,从左到右,一条条分支的找。这种情况下,经典和新式类寻找都一样:
DAE > B > C
情况二
新式类:HEB>FC>GDA 最后一个分支时才找到头。广度优先。
经典类:一条分支找到头,再找下一个分支。深度优先。
HEBA > FC > GD
class A: pass def foo(self): print('from A') class B(A): pass # def foo(self): # print('from B') class C(A): pass def foo(self): print('from C') class D(A): pass def foo(self): print('from D') class E(B): pass # def foo(self): # print('from E') class F(C): pass def foo(self): print('from F') class G(D): pass def foo(self): print('from G') class H(E,F,G): pass # def foo(self): # print('from H') obj = H() obj.foo() # 经典类打印结果是 from A # 经典类查找:HEBA > FC > GD 深度优先,一个分支找到头,再找下一个分支 # 如果让class A(object) 继承object,成为新式类,那么,打印结果是 from F # 新式类查找:HEB > FC > GDA > (object) 广度优先,在F时找到
再说self
self就是调用方法的对象本身!无论何时,找方法时先回自己类开始找!class A(object): def f3(self): print('A.f3') class B(object): def f1(self): print('B.f1') self.f2() class C(B): def f2(self): print('C.f2') self.f3() def f3(self): print('C.f3') class D(C): def f3(self): print('D.f3') obj = D() obj.f1() ''' 打印结果 B.f1 C.f2 D.f3 '''
mro()继承属性的查找顺序
print(H.mro()) 查看继承,只在新式类有这个属性:[<class '__main__.H'>, <class '__ b913 main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
子类调用父类的方法
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Teacher(People): def __init__(self,name,age,sex,level): # 指明道姓的调用父类的属性 People.__init__(self,name,age,sex,) # 明确写出父类的名称,如果父类改了,那么这里也要改。 self.level=level class Student(People): def __init__(self,name,age,sex,group): # 通过super()调用父类的属性 super().__init__(name,age,sex) # super()自动传入'Student' 'self'两个参数,产生一个对象 # 对象调用函数是绑定方法,因此__init__()的第一个参数self也不用手动传入了。 self.group=group
在python2中使用
super()子类名和self这个两个参数还是要手动传,即
super(子类名,self)
另外,只有新式类才可以使用
super()函数,因为该函数在寻找继承属性时,是根据mro列表。并且是只要找到一个父类的属性,就会停止。因此,如果子类中要调用多个父类中的同名属性时,还是要用指名道姓的方式。
相关文章推荐
- Python(面向对象编程——2 继承、派生、组合、抽象类)
- Python(面向对象编程——继承、派生、组合、抽象类)
- 2015/9/22 Python基础(18):组合、派生和继承
- python基础之类的继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法
- Python开发基础-Day18继承派生、组合、接口和抽象类
- python基础----继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法
- 1.6、Python(面向对象编程——2 继承、派生、组合、抽象类)
- Python基础(6)- 类和对象(使用、继承、派生、组合、接口、多态、封装、property、staticmethod、classmethod、反射、slots、上下文管理协议、元类)
- DAY31继承派生、组合、接口和抽象类
- python基础之继承派生、组合、接口和抽象类
- iOS 工作之余小总结(二)类的高度总结----封装,继承,派生,重写,多态,协议,分类,扩展,组合,聚合。
- Python基础(16)_面向对象程序设计(类、继承、派生、组合、接口)
- Python 继承、派生、组合、接口、抽象类
- python面向对象之继承与派生
- C++讲义 第8章 继承与派生
- what is 继承与派生
- UML中几种类间关系:继承、实现、依赖、关联、聚合、组合的联系与区别
- 飛飛(五十六)班长与学生的信息(继承和派生的应用)
- 【组合】 达到了继承的效果,又不破坏封装
- 继承与派生