类的三大特性之继承

一 初识继承

一 什么是继承?

继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可成为基类或超类,新建的类称为派生类或子类.

子类会继承父类的属性,从而解决代码重用问题.

python中类的继承分为:单继承和多继承

class Parent1:
    pass

class Parent2:
    pass

class Sub1(Parent1): # 单继承,基类是Parent1,派生类是Sub
    pass

class Sub2(Parent1,Parent2): # 多继承,用逗号分隔开多个继承的类
    pass

查看继承

print(sub2.__bases__) # __base__只查看继承的第一个子类,__bases__则是查看所有继承的父类

经典类与新式类

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

python3中如果没有指定基类,python的类会默认继承object类,object类是所有python类的基类,它内置了一些方法.

二 继承与抽象

继承描述的是子类与父类的继承关系,想要找出这种关系,必须先抽象再继承.

抽象:即抽取类似或者相似的部分

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构

二 继承的使用场景

三 继承与重用性

在开发程序的过程中,如果我们首先定义了一个A类,然后又想新建一个B类,但是B类的大部分内容与A类代码相同时,可以通过继承的方法来实现代码重用.

B继承A会获得A的所有属性(数据属性与函数属性),所以B中没有而A中具有的属性可以被B用来使用,而B中有的就使用自己的:

class A:
    a = 123
    print(a)
    
    def func1(self):
        print('A, func1')
        
    def func2(self):
        print('A, func2')
        self.func1()
        
class B(A):
    a = 134
    def func1(self):
        print('B, func1')
        
b = B()
print(b.a)
b.func2()

# 运行结果
123 # A为B的父类,在继承的时候会首先执行A类中的代码
134
A, func2
B, func1

四 派生类

虽然子类是继承了父类的的属性,但是子类也可以添加自己新的属性或者对子类生成的对象定义自己的属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类同名时,那么调用新的属性时以子类的为准.

class A:
    a = 123
    print(a)
    
    def func1(self):
        print('A, func1')
        
    def func2(self):
        print('A, func2')
        self.func1()
        
class B(A):
    a = 134
    def func1(self):
        print('B, func1')
        
    def func3(self):
        print('B, func3')

B定义了自己新的属性func3,而且该函数和A类没有关系.

五 组合与重用性

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

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

class Equip: #武器装备类
	def fire(self):
		print('release Fire skill')
        
class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
	camp='Noxus'
	def __init__(self,nickname):
		self.nickname=nickname
		self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 
r1=Riven('锐雯雯')
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
# 运行结果
release Fire skill

组合与继承都是有效的利用已有类的资源的重要方式.但是二者的概念和使用场景皆不同.

  1. 继承的方式

通过继承建立了派生类和父类之间的关系,它是一种从属关系.

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好

  1. 组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种附带关系.

六 抽象类

  1. 什么是抽象类?

与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化.

  1. 为什么要有抽象类?

如果说类是从一堆对象中抽取相同的内容得来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性.

从设计角度看,抽象类与普通类的不同之处在于:抽象类中只能抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法.

三 继承实现的原理

一 继承顺序

在python中可以同时继承多个父类,如A(B,C,D)

如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到要找的属性,找完了还没有的话会报属性不存在的错误.

如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先.

class A(object):
    def test(self):
        print('from A')

class B(A):
    def test(self):
        print('from B')

class C(A):
    def test(self):
        print('from C')

class D(B):
    def test(self):
        print('from D')

class E(C):
    def test(self):
        print('from E')

class F(D,E):
    # def test(self):
    #     print('from F')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

二 继承原理

python是如何实现继承并查找属性的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表.

为了实现继承,python会在MRO列表中从左到右开始查找基类,直到找到第一个匹配这个属性的类为止.而这个MRO列表的构造是通过一个C3线性化算法来实现的.它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  1. 子类会先于父类被检查
  2. 多个父类会根据它们在列表中的顺序被检查
  3. 如果对下一个类存在两个合法的选择,选择第一个父类

查找属性时,即使没有直接继承关系,也会按照MRO列表继续往后查找,当使用super()函数时,python会在MRO列表上继续搜索下一个类.只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次

注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,不要在意继承关系,看MRO列表就好

三 super的使用

BaseTher类的创建

# coding: utf-8
# @Time    : 2018/10/23 11:40 PM
# @Author  : MUSIBII
# @Email   : shaozuanzuan@gmail.com
# @File    : father_type.py

import os, sys
import pickle
import hashlib

sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import db_file

'''
	11、抽象老师类与学生类得到父类,用继承的方式减少代码冗余

'''

import time


class BaseTher:

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
        self.time = time.time()
        # self.id_hash = self.create_id()

    # 创建唯一的id
    def create_id(self):
        # timenow = str(time.time()).split('.')[0]
        key = self.name + str(self.age) + self.gender + str(self.time)
        m = hashlib.md5(key.encode('utf-8'))
        self.id_hash = m.hexdigest()
        # print(m.hexdigest())
        # return m.hexdigest()

    def tell_info(self):
        data = self.get_obj_by_id()
        for item in data:
            print('%-10s: %-20s' % (item, data[item]))
        # print(data)

    # 将信息序列化后保存到文件
    def save(self):
        user_data = {'name': self.name, 'age': self.age, 'gender': self.gender, 'id_hash': self.id_hash}
        with open('db_file/%s' % self.id_hash, 'wb') as f:
            pickle.dump(user_data, f)

    # 通过id反序列化信息
    def get_obj_by_id(self):
        with open('db_file/%s' % self.id_hash, 'rb') as f:
            return pickle.load(f)

子类通过super().init(*args)使用父类的__init__方法

class Teacher(BaseTher):
    def __init__(self, name, age, gender, level, salary):
        super().__init__(name, age, gender)
        self.level = level
        self.salary = salary
        # self.id_hash = self.create_id()

    # 创建唯一的id
    def create_id(self):
        # timenow = str(time.time()).split('.')[0]
        key = self.name + str(self.age) + self.gender + str(self.level) + str(self.salary)
        m = hashlib.md5(key.encode('utf-8'))
        self.id_hash = m.hexdigest()
        # print(m.hexdigest())
        # return m.hexdigest()

    # def tell_info(self):
    #     data = self.get_obj_by_id()
    #     for item in data:
    #         print('%-10s: %-20s' % (item, data[item]))
    #     # print(data)

    # 将老师信息序列化后保存到文件
    def save(self):
        user_data = {'name': self.name, 'age': self.age, 'gender': self.gender, 'level': self.level, 'salary': self.salary, 'id_hash': self.id_hash}
        with open('db_file/%s'%self.id_hash, 'wb') as f:
            pickle.dump(user_data, f)

    # # 通过id反序列化老师信息
    # def get_obj_by_id(self):
    #     with open('db_file/%s'%self.id_hash, 'rb') as f:
    #         return pickle.load(f)

在Python3中,super()括号里面可以为空,不过也可以写成super(本类的名字,self)

像上面的super().init(name, age, gender),

可以写成super(Teacher,self).init(name, age, gender)

四 组合的使用

  1. 什么是组合?

一个对象的属性是来自另外一个类的对象,称之为组合

  1. 为何用组合?

组合也是用来解决类与类代码冗余的问题

  1. 组合的用法如下代码:
# coding: utf-8
# @Time    : 2018/10/24 10:27 AM
# @Author  : MUSIBII
# @Email   : shaozuanzuan@gmail.com
# @File    : zuhe.py


class OldboyPeople:
    school = 'Oldboy'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class OldboyStudent(OldboyPeople):
    def __init__(self, name, age, gender):
        super().__init__(name, age, gender)

    def choose_course(self):
        print('%s is choosing course' % self.name)


class OldboyTeacher(OldboyPeople):
    def __init__(self, name, age, gender, level, salary):
        OldboyPeople.__init__(self, name, age, gender)
        self.level = level
        self.salary = salary

    def score(self, stu, num):
        stu.num = num
        print('老师%s给学生%s打分%s' % (self.name, stu.name, num))


class Course:
    def __init__(self, course_name, course_price, course_period):
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period

    def tell_course(self):
        print('课程名:<%s> 价钱:[%s] 周期:[%s]' % (self.course_name, self.course_price, self.course_period))


course = Course('python', 3000, '5months')

stu1 = OldboyStudent('musibii', 18, 'male')
stu1.course = course

print(stu1)
posted @ 2018-10-23 20:06  rsuxwvilc  阅读(177)  评论(0编辑  收藏  举报