面向对象 组合 继承
继承
什么是继承
继承:是一种关系,描述两个对象之间什么是什么的什么的关系
例如:哈士奇,金毛,阿拉斯加,拉布拉多,柯基都是狗
为什么要继承
继承的好处:# 继承的一方可以直接使用被继承一方已经有的东西
在程序中,继承描述的是类和类之间的关系
例如:a继承了b,a就能直接使用b已经存在的方法和属性
此时,a称之为子类,b称之为父类,也称之为基类。
为什么使用继承:# 其目的是为了重用已经有了的代码,提高重用性
如何使用继承
语法
class 类名称(父类的名称): # 在python中 一个子类可以同时继承多个父类
继承小案例(子类直接用父类的方法,无需自己实现)
class Base: desc = "这是一个基类" def show_info(self): print(self.des) @staticmethod def make_money(): print("一天赚ta一个亿") class SubClass: @staticmethod def make_money(): print("一天赚ta一百") pass class SubClass2(Base): # 通过继承使用父类的 make_money pass # 无继承 obj1 = SubClass() obj1.make_money() # 一天赚ta一百 # 继承,可得到父类的方法及属性 obj2 = SubClass2() obj2.make_money() # 一天赚ta一个亿 print(obj2.desc) # 这是一个基类
管理学生与老师的案例(老师类默认有教书的方法,而学生类是不可以有的,所以不能直接让学生类继承老师类)
# 需求:管理老师 class Teacher: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def say_hi(self): print(f"name:{self.name},gender:{self.gender},age:{self.age}") t1 = Teacher('jack', 'male', 20) t1.say_hi() # name:jack,gender:20,age:male # 扩展需求:把老师也一起管理 class Student: def __init__(self, name, age, gender, number): self.name = name self.age = age self.gender = gender self.number = number def say_hi(self): print(f"name:{self.name},gender:{self.gender},age:{self.age}") s1 = Student('sushan', 'female', 18, 'xxx01') s1.say_hi() # name:sushan,gender:18,age:female
上面代码有些重复,学生和老师有很多属性都是一样的。
抽象
直意:不具体、不清晰、很模糊、看不太懂
编程中:# 将多个子类的中相同的部分,进行抽取,形成一个新的类,这个过程也称之为抽象
# 抽取老师学生的共同特征,然后再继承 class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender def say_hi(self): print(f"name:{self.name},gender:{self.gender},age:{self.age}") pass class Teacher(Person): def teaching(self): print("老师教学生,写代码....") class Student(Person): pass t1 = Teacher('jack', 'male', 20) t1.say_hi() # name:jack,gender:20,age:male t1.teaching() # 老师教学生,写代码.... s1 = Student('rose', 'female', 20) s1.say_hi() # name:rose,gender:20,age:female # s1.teaching() # 报错,找不到teaching(他没有,他的父类也没有)
如何正确使用继承
1. 先抽象(提取特征)再继承
2. 继承一个已经现存的类,作战货值修改原始的功能
class A: text = 'haha' class B(A): text = 'heihei' # 注释掉访问父级的 pass b = B() print(b.text) # b自身没有,找类,就不用访问类的父类的了 # heihei b.text = 'xixix' print(b.text) # b(对象)自身有,就不能找类了 # xixix
属性的查找顺序
查找顺序:对象自身 --> 类 --> 父类 --> ...父类的上级父类... --> Object --> 报错
派生与覆盖(重写)
- 派生:
# 当一个子类中出现了与父类中不同的内容时,这个子类就称之为派生类
class Person: @staticmethod def say_hi(): print("hello") # 这个Student子类不是派生,父类Person一模一样。(这样没啥意思) class Student(Person): pass
通常子类都会写一些新的代码,不可能和父类完全一样,即通常子类都是派生类
派生类就是子类的意思
- 覆盖:
# 也称之为重写(overrides)当子类出现了与父类名称完全一样的属性或是方法,就是覆盖
class Person: @staticmethod def say_hi(): print("hello") # 这个Student子类不是派生,父类Person一模一样。(这样没啥意思) class Student(Person): @staticmethod def say_hi(): # 与父类的say_hi 重复,重写、覆盖 print("hello world!") pass s = Student() s.say_hi() # hello world!
练习:实现一个可以限制元素类型的容器(子类访问父类中的内容)
补充知识点
子类访问父类的方法:# super(当前类名称, self).你要调用的父类的属性或方法
# 小练习:做一个可以限制元素类型的容器类型 class MyList(list): # 继承list,可以直接用list的一些方法属性 def __init__(self, data_type): super(MyList, self).__init__() # 应规范,子类重写父类方法的时候__init__初始化函数中要调用父类的__init__初始化函数 self.data_type = data_type def append(self, obj): ''' 重写父类的append方法 :param obj: 是要存储的元素 :return: None ''' if isinstance(obj, self.data_type): # if type(obj) == self.data_type: # 写法二 super(MyList, self).append(obj) # 这里需要访问父类的append 方法来完成真正的存储操作 else: print(f"非指定类型{self.data_type}!") # 创建时指定要存储的元素类型 str_list = MyList(str) str_list.append('abc') print(str_list[0]) # abc str_list.append(1) # 非指定类型<class 'str'>!
访问父类属性的三种方式
# 1.super(类, 对象自身).类的属性/方法 python2的写法(兼容写法,python2、3都可以用) # 2.super().类的属性/方法 python3的新语法 ***** (推荐,python2项目慎用哦) # 3.类.属性/方法 没啥实际意义,不是继承,这是直接用类来调用了
代码案例
# 子类访问父类中的属性 class Parent: text = 'abc' @staticmethod def say_something(): print("anything") class Sub(Parent): def show_info(self): # # 方式一: python2 和 3 都兼容 print(super(Sub, self).text) super(Sub, self).say_something() # # # 方式二:python 3 中的语法 *** 推荐 print(super().text) super().say_something() # # # 方式三:没啥意义,不是继承,指名道姓的调用 print(Parent.text) Parent.say_something() pass s = Sub() s.show_info() # ----- 方式一 # abc # anything # ----- 方式二 # abc # anything # ----- 方式三 # abc # anything
强调点
如果子类继承了一个现有的类,并且覆盖了父类的__init__
方法时,那么必须在__init__
方法中的第一行必须调用父类中的__init__
方法,并传入父类所需的参数。 --- 这是重点 ---
上面案例改版(没有调用父类的__init__
方法,父类可能没有初始化完成,后续可能会导致一些意想不到的问题)
class Person: def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age self.say_hello() # 初始化时要调用的函数 def say_hi(self): print(f"name:{self.name},gender:{self.gender},age:{self.age}") def say_hello(self): print(f"Hello, i'm {self.name}") class Student: def __init__(self, name, gender, age, number): self.name = name self.gender = gender self.age = age self.number = number def say_hi(self): print(f"name:{self.name},gender:{self.gender},age:{self.age}") print(f"number:{self.number}") # 上述代码优点冗余,怎么简化? class Student2(Person): def __init__(self, name, gender, age, number): super().__init__(name, gender, age) # 不调用父类的__init__方法就会使父类的初始化函数中的say_hello方法,初始化就不能算是完成 *** self.number = number def say_hi(self): super().say_hi() print(f"number:{self.number}") stu = Student2("rose", 'female', 18, 'young1') # Hello, i'm rose stu.say_hi() # name:rose,gender:female,age:18 # number:young1
组合
组合:
# 也是一种关系,描述的是两个对象之间是什么有什么的关系,将一个对象作为另一个对象的属性(即什么有什么)
例如:学生有手机、游戏中的角色拥有某些装备
组合无处不在,数据类型、函数都是对象,都有组合
组合的目的:# 重用现有代码
# 让学生使用手机打电话、发短信 class Phone: def __init__(self, price, kind, color): self.price = price self.kind = kind self.color = color @staticmethod def call(): print("正在呼叫xxx...") @staticmethod def send_msg(): print("正在发送....") class Student: def __init__(self, name, gender): self.name = name self.gender = gender def show_info(self): print(f"name:{self.name}, gender:{self.gender}") # 让学生拥有打电话这个功能(有联系) stu1 = Student('rose', 'female') phone1 = Phone(1888, 'vivo', 'red') phone1.call() # 正在呼叫xxx... # 组合:把一个对象作为另一个对象的属性 class Student2: def __init__(self, name, gender, phone): self.name = name self.gender = gender self.phone = phone def show_info(self): print(f"name:{self.name}, gender:{self.gender}") phone2 = Phone(1888, 'vivo', 'red') stu2 = Student2('rose', 'female', phone2) stu2.phone.call() # 正在呼叫xxx... stu2.phone.send_msg() # 正在发送....
组合与继承的取舍
''' 继承:分析两个类的关系,到底是不是:什么是什么的关系 组合:如果两个类之间,没有太大的关系,完全不属于同类 另外:组合相比继承,耦合度更低 '''
菱形继承
多继承带来的问题:python支持多继承,虽然灵活,但会带来名称冲突的问题(到底找谁的
新式类与经典类
python3 中任何类都是直接或间接继承自object
新式类:任何显式或隐式地继承自object的类就称之为新式类(即python3 中的类全是新式类)
经典类:不是object的子类,仅在python2 中出现
扩展
# 在python2 中可能有这样子的代码 class Person(object): # 默认让python2 中的类也是新式类,兼容写法 pass
mro列表(只有python3中)
调用方式:# 类.mro() --> 可以获取到类的 **mro 列表**,里面的元素就是类的查找顺序
class Parent: pass class Sub(Parent): pass print(Sub.mro()) # [<class '__main__.Sub'>, <class '__main__.Parent'>, <class 'object'>] # 从左到右就是这个类的查找顺序,先Sub自身 再Parent 再object
类的属性的查找顺序
新式类中的菱形查找
新式类中的查找顺序
类的属性查找顺序
新式类:先找自身,再先深度找,如果有共同父类再广度找(直接看类的mro列表就知道查找顺序了 类.mro() )
经典类: python2中的经典类就是深度优先
# 此段代码指定时python2 运行 # 注释掉不同类中的num 来测试查找顺序 class B: # num = 2 pass class C: # num = 3 pass class E(B): # num = 5 pass class F(C): # num = 6 pass class G(C): num = 7 pass class H(E, F, G): # num = 8 pass print(H.num) # print(H.mro()) # python2 中没有 mro() # [<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.F'>, <class '__main__.G'>, <class '__main__.C'>, <class 'object'>] # [H, E, B, F, G, C, object] ---> 上面的mro简化表示顺序(这是python3 的顺序) # [H, E, B, F, C, G, object] ---> 这是python2 的顺序