面向对象之 —— 继承

  OOP开发大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现。  

继承与抽象(先抽象,再继承)

抽象:抽取多个类中相同的部分形成另一个类

  通过抽象避免了继承一些不应该有的内容,抽象过程中,可能会有一些与业务无关的内容,这是正常的,这些是公共父类

继承描述的是子类与父类间关系,其基于抽象的结果

公共父类的作用:存储多个子类相同的属性和技能

继承

  继承是一种关系,必须存在两个对象才能产生这种关系,被继承称为父(基/超类(Base class、Super class)),继承为子(派生类)。子类会“遗传”父类的属性,从而解决代码重用问题

  程序中继承指的是类与类之间的关系,使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

class Parent:   #父类
    year = 2018
    def coding(self):
        print("正在编程........")

class Sub(Parent):    #子类
    pass
print(Parent.year)
print(Sub.year)
# Sub.coding()
s = Sub()
print(s.year) # 子类可以使用父类中的属性
s.coding() # 子类也可以父类中的函数

#2018 
 正在编程

继承与重用性

  在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。

  用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,这就是软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期

注意:属性引用,会先从实例中找然后去类中找,然后再去父类中找...直到最顶级的父类。

 

  在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

单继承 

class Person(Object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def walk(self):
        print("%s is walking..." % self.name)

    def talk(self):
        print("%s is talking..." % self.name)


# 定义子类1  py2\3都支持
class Teacher(Person):
    def __init__(self, name, age, level):
        super(Teacher, self).__init__(name, age)  # python提供了这个函数,创建一个父类的对象,用来调用父类的__init__()
        # 避免了再次定义这些属性
        self.level = level

    def walk(self):  # 继承父类的方法并 拓展 实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
        super().walk()  # 避免再写一遍父类中的功能代码
        print("加速...")

    def teach(self):
        print("%s is teaching..." % self.name)


# 定义子类2
class Student(Person):
    def __init__(self, name, age, grade):
        Person.__init__(self, name, age)  # 这样写避免了自己再次定义这些属性
        self.grade = grade

    def fight(self):
        print("%s is fighting..." % self.name)


t1 = Teacher("mr z", 30, "一级")
s1 = Student("xiao ming", 15, "高中一年级")
单继承
 

  就像上述代码中,定义Person 类时,class Person(Object)  表示Person 类拥有Object 类的属性和方法(一些内置方法和属性),class Teacher(Person),Teacher 类拥有Person 类的属性和方法...(walk和talk). Person 是Teacher 的父类,Teacher 是Person 的子类.

Python 有两个判断继承的函数:

isinstance() 用于检查实例类型;issubclass() 用于检查类继承。

参见下方示例:

class Person(object):
    pass

class Child(Person):                 # Child 继承 Person
    pass

May = Child()
Peter = Person()    

print(isinstance(May,Child))         # True
print(isinstance(May,Person))        # True
print(isinstance(Peter,Child))       # False
print(isinstance(Peter,Person))      # True
print(issubclass(Child,Person))      # True

类名.__bases__: 返回该类继承于哪些类(多继承时的从左到右的类都将被返回)
类名.__base__: 只返回从左到右继承的第一个类.
类名.__mro__ / 类名.mro():可以返回该类的查找顺序列表
在最上面的实例中 : print(Teacher.mro()) 打印[<class '__main__.Teacher'>, <class '__main__.Person'>, <class 'object'>]

多继承

多继承是指一个类有多个父类的情况(以,隔开)。多继承使用的比较少,但是需要知道,多继承时的继承顺序。

SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类

1、Python的类可以继承多个类,Java和C#中则只能继承一个类

2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先广度优先

  • 当类是经典类时,多继承情况下,会按照深度优先方式查找
  • 当类是新式类时,多继承情况下,会按照广度优先方式查找

首先提出两个概念:新式类和经典类.

新式类:所有直接继承或间接继承object的类都是(python3中默认所有类都是新式类)。创建对象时需要申请名称空间,将对象属性放进去,这些事情在新式类里,都是objeck类自动执行

经典类:属性查找按照深度优先(python2中默认都是经典类)。继承于哪个类都需要在()里说明,没有显式的继承object类的类,以及该类的子类,都是经典类 

python2与python3 的区别
  1.只有在python2中才分新式类和经典类,python3中统一都是新式类   2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类   3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类   4.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
经典类与新式类:

class S:
    pass
class Student(S):
    pass

# __bases__用于查看父类
print(Student.__bases__)

# 显示属性的查找顺序列表,属性查找属性就是按照该列表来查找的
print(Student.mro())

  Python 与其他语言不同点在于,当我们定义一个 class 的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”,都会获得所有“新式类”的特性.反之,即不由任意内置类型派生出的类,则称之为“经典类”。 “新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x之后的版本,因为所有的类都派生自内置类型objec.(即使没有显示的继承object类型),即所有的类都是“新式类”。
 

经典列和新式类在继承顺序(继承的查找)方式上有区别:

  • 新式类的继承顺序是按照C3算法来的
  • 经典类则是按照“从左至右,深度优先”的方式去查找属性。当有菱形继承时,祖先类最后查找.

注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

C3算法

 merge操作是C3算法的核心。遍历执行merge操作的序列,如果一个序列的第一个元素,是其他序列中的第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。

C3:合并所有父类的MRO列表并遵循如下三条准则:
    1.子类会先于父类被检查
    2.多个父类会根据它们在列表中的顺序被检查
    3.如果对下一个类存在两个合法的选择,选择第一个父类
# 简易的定义几个类
class A(object): pass

class B(object): pass

class C(object): pass

class E(A, B): pass

class F(B, C): pass

class G(E, F): pass
'''
O表示object类
A、B、C都继承至一个基类,所以mro序列依次为[A,O]、[B,O]、[C,O]
mro(E) = [E] + merge(mro(A), mro(B), [A,B])
       = [E] + merge([A,O], [B,O], [A,B])
执行merge操作的序列为[A,O]、[B,O]、[A,B]
A是序列[A,O]中的第一个元素,在序列[B,O]中不出现,在序列[A,B]中也是第一个元素,所以从执行merge操作的序列([A,O]、[B,O]、[A,B])中删除A,合并到当前mro,[E]中。
mro(E) = [E,A] + merge([O], [B,O], [B])
再执行merge操作,O是序列[O]中的第一个元素,但O在序列[B,O]中出现并且不是其中第一个元素。继续查看[B,O]的第一个元素B,B满足条件,所以从执行merge操作的序列中删除B,合并到[E, A]中。
mro(E) = [E,A,B] + merge([O], [O])
       = [E,A,B,O]
'''
C3算法

mro

super获取父类内容时,按照mro列表来查找,mro列表的构造是通过C3线性算法实现的。

派生

子类继承某父类且有自己独特的属性/技能

一旦重新定义了自己的属性且与父类重名,那么调用的属性时,就以自己为准。

注意:在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值

子类出现了与父类重复的名字称之为覆盖。

子类访问父类中的方法

  • I.指名道姓的调用,与继承无关
  • II.super()表示创建一个特殊对象,用于调用父类的方法,其会参照类的mro列表一次查找属性
super().__init__(name,age,sex)

 

在python2中有不同方式,super需要传入当前类/对象

super(Student,self).__init__(name,age,sex).

 

存在继承关系后的属性查找

对象->类->父类->父类的父类
  优先找对象,若对象没有,则找类,如果类没有,会沿着继承关系一直找到最顶层的父类
多层继承关系中深度优先,沿着一条继承路径找到底,若没有再找其他路径(按从左到右依次查找)
此情况仅在非菱形继承时使用

菱形

当存在共同父类时,会产生菱形继承关系
深度优先->广度优先
若父类中既有菱形又有非菱形,先按照继承顺序查找,若有菱形则按深度->广度

 

使用python3的新式类时,Object为默认的父类,所有的类都会继承它的一些属性和方法,它们的名称是以双下划线__开头,同时又以双下划线__结尾。

这些属性和方法不再是私有属性和私有方法,它们是可以在类的外部通过实例对象去直接访问的,且它们都有着各自特殊的意义,我们可以通过这些特殊属性和特殊方法来获取一些重要的信息,或执行一些有用的操作

属性:

属性名称 说明
__doc__ 类的描述性信息 类似函数的''' ''' 注释
__module__ 当前操作对象的类的定义所在模块名
__class__ 当前操作的对象的类名
__dict__ 一个字典,保存类的所有成员(属性和方法)或实例对象的成员属性

方法:

方法 说明
__init__ 初始化构造方法,创建类时自动执行
__del__ 析构方法,当对象在内存中被释放(回收)前,会自动执行
__str__ 如果类中定义了__str__方法,并有返回值,在打印对象时会自动输出_-str__的返回值
__xxxitem__

是指__getitem__、__setitem__、__delitem这3个方法,它们用于索引操作,比如对字典的操作,分别表示 获取、设置、删除某个条目。 数据。可以通过这些方法来定义一个类对字典进行封装,

从而可以对字典中key的操作进行控制,尤其是删除操作。

__new__ 该方法会在__init__方法之前被执行,该方法会创建被返回一个新的实例对象,然后传递给__init__。另外需要说明的是,这不是一个成员方法,而是一个静态方法。
__call__ 源码中的注释是"Call self as a function." 意思是把自己(实例对象)作为一个函数去调用,而函数的调用方式是函数名()。也就是说,当我们执行实例对象()或者 类名()()这样的操作时会触发执行该方法。

 

组合

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合:在一个类中以另外一个类的对象作为数据属性,称为类的组合

组合是对象之间的关系 

组合的目的:降低冗余,降低耦合度

组合与继承

1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种''的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Teacher(People):
    def __init__(self,name,age,sex,job):
        People.__init__(self,name,age,sex)
        self.job_title=job_title
        self.course=[]
        self.students=[]


class Student(People):
    def __init__(self,name,age,sex):
        People.__init__(self,name,age,sex)
        self.course=[]


lh=Teacher('李华',38,'male',' 老师')
xm=Student('小明',18,'female')

python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)

#为老师和学生添加课程
lh.course.append(python)
lh.course.append(linux)
xm.course.append(python)

#为老师添加学生
lh.students.append(xm)


#使用
for obj in lh.course:
    obj.tell_info()
继承与组合

 

posted @ 2019-05-29 14:43  呔!妖精。。。  阅读(84)  评论(0编辑  收藏  举报