您的位置:首页 > 编程语言 > Python开发

继承、派生、组合

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 继承 组合
相关文章推荐