面向对象 (OOP) 的三大特征:# 封装, 继承, 多态
继承
什么是继承
继承:# 是一种关系, 描述两个对象之间什么是什么的什么的关系
例如: 麦兜, 佩奇, 猪猪侠, 猪刚鬣, 都是猪
为什么要使用继承
继承的好处:# 继承的一方可以直接使用被继承一方已经有的东西
在程序中, 继承描述的是类和类之间的关系
例如: 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
当使用 super()函数时, python3 会在 mro 列表上继续搜索下一个类. 如果每个重定义的方法统一使用 super()并只调用它一次, 那么控制流最终会遍历完整个 mro 列表, 每个方法也只会被调用一次
注意注意注意: 使用 super 调用的所有属性, 都是从 mro 列表当前的位置往后找, 千万不要通过看代码去找继承关系, 一定要看 mro 列表
类的属性的查找顺序
新式类中的菱形继承
新式类中的查找顺序
类的属性查找顺序:
新式类: 先找自身, 再先深度找, 如果有共同父类再广度找(直接看类的 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 的顺序
初次用 Markdown 上传博客哦, 如有不好还请见谅~
来源: https://www.cnblogs.com/suwanbin/p/11246877.html