类的继承实例

继承

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。

被继承的类称为“基类”、“父类”或“超类”。

继承的过程,就是从一般到特殊的过程。

要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

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

继承概念的实现方式主要有2类:实现继承、接口继承。

Ø         实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø         接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法);
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。
 
抽象类仅定义将由子类创建的一般属性和方法。

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

下面我们来看个继承的简单实例:

类的继承实例_经典模式

类的继承实例_python_02

如果子类有一个跟父类一样的方法:

类的继承实例_经典模式_03

我们现在给父类增加两个参数,name,age。

类的继承实例_父类_04

这个时候调用子类用方法b=BlackPerson(),必须带上参数name和age,否则就会报错。如下图:

类的继承实例_经典模式_05

子类调用必须加上参数如下图:

类的继承实例_经典模式_06

如果我想给子类传参数,不可以在子类下面增加初始化函数。

类的继承实例_初始化_07

我们应该先继承再重构函数:

类的继承实例_初始化_08

类的继承实例_初始化_09

甚至在子类里可以自定义新的属性了,如下图:

类的继承实例_父类_10

 现在我们来看一个完整的学校,老师和学生的例子:

class SchoolMember(object):
    '''学校成员基类'''
    member = 0
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
        self.enroll()
    def enroll(self):
        '''注册'''
        print("just enrolled a new school member [%s]"% self.name)
        SchoolMember.member +=1

    def tell(self):
        print("------info:%s-----"%self.name)
        for k,v in self.__dict__.items():
            print("\t",k,v)

        print("------end-----")


    def __del__(self):
        print("开除了[%s]..."% self.name )
        SchoolMember.member -=1

class School(object):
     '''学校类'''
     def open_branch(self,addr):
         print("openning a new branch in ",addr)


class Teacher(SchoolMember,School):
    '''讲师类'''
    def __init__(self,name,age,sex,salary,course):
        #SchoolMember.__init__(self,name,age,sex) #经典类写法
        super(Teacher,self).__init__(name,age,sex) #新式类写法
        self.salary = salary
        self.course = course
    # def tell(self):
    #     print("""--------info:%s
    #         name:%s
    #         age:%s
    #         salary :%s
    #     """ % (self.name,self.name,self.age,self.salary))
    def teaching(self):
        print("Teacher [%s] is teaching [%s]" % (self.name,self.course))

class Student(SchoolMember):
    def __init__(self,name,age,sex,course,tuition):
        SchoolMember.__init__(self,name,age,sex)
        self.course = course
        self.tuition = tuition #fee
        self.amount = 0
    def pay_tuition(self,amount):
        print("student [%s] has just paied [%s]" %(self.name,amount))
        self.amount += amount

t1 = Teacher("Wusir", 28, "F*M",3000,"Python" )
s1 = Student("HaiTao", 38,"N/A","PYS15", 300000 )
s2 = Student("LIChuang",12,"M","PYS15", 11000 )

print(SchoolMember.member)
t1.tell()
t1.open_branch("SH")
s2.tell()
del s2

print(SchoolMember.member)

上面的例子,几个注意要点:

类的继承实例_初始化_11

类的继承实例_子类_12

这必须用类名SchoolMember而不能用self,如果用self就没法实现全局变量进行累加的效果了~!

我们如果想打印老师和学生,实例的所有信息,怎么便捷的操作呢?

我们发现实例后面.__dict__可以用字典的方式把所有的信息打印出来:

类的继承实例_父类_13

那么我们可以在父类直接加上循环打印实例信息。

类的继承实例_初始化_14

在上面代码里,还有一个多继承的问题,也就是老师其实继承了2个父类,一个SchoolMember一个是School。

类的继承实例_经典模式_15

最后我们来看下继承父类有两个写法,新式类VS经典类:

类的继承实例_python_16

其实新式类的定义class Person(object),这个object也是一个系统默认的类,那么为什么python要让所有的类的父类都是object呢?

因为万一以后python想给类class加什么新功能,它直接加在class object的方法就可以,这样就可以无限扩展了!所以推荐用新式类的写法来给类定义。

类的继承实例_初始化_17

现在我们再来看一个在经典模式和新式模式下,构建函数,初始化的查找顺序问题:

在默认的情况下,见下面的代码:

class A(object):
    def __init__(self):
        self.n = "A"
class B(A):
    # pass
    def __init__(self):
        self.n = "B"
class C(A):
    #pass
    def __init__(self):
        self.n = "C"
class D(B,C):
    # pass
    def __init__(self):
        self.n = "D"
d = D()
print(d.n)

打印出来的结果肯定是D,这个时候搜索的最近的初始化。

如果我们把D的初始化注释掉:

类的继承实例_子类_18

这个时候打印出来的结果是B;

同样我们把B注释掉,打印出来的是C;

再把C注释掉,打印出来的是A。

我们来看下面的关系图:

类的继承实例_父类_19

查找的顺序是D->B->C->A,也就是在PY3里,查找的顺序是按广度查询的方式,先在同级的class查找,然后再按深度查询的方法,查找到A。

同样在经典模式的情况下,也就是把A的object去掉:

类的继承实例_经典模式_20

这个时候在PY3里查找的顺序依旧是D->B->C->A。

但是注意,有特殊的情况,如果在PY2里,经典模式的查找顺序就是D->B->A->C,在PY2里查找的顺序优先是深度查找,然后才是广度查询!