专栏首页小码农漫漫积累路python面向对象的继承-组合-02

python面向对象的继承-组合-02

面向对象(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 的顺序

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Django-djangorestframework-异常模块-源码及自定义异常

    E:/python3-6-4/Lib/site-packages/rest_framework/views.py 源码

    suwanbin
  • python面向对象的多态-类相关内置函数-类内置魔法函数-迭代器协议-上下文管理-04

    首先强调,多态不是一种特殊的语法,而是一种状态,特性(多个不同对象可以相应同一个方法,长身不同的结果)

    suwanbin
  • python面向对象-封装-property-接口-抽象-鸭子类型-03

    # 将复杂的丑陋的隐私的细节隐藏到内部,对外提供简单的使用接口 或 # 对外隐藏内部实现细节,并提供访问的接口

    suwanbin
  • 用最小二乘法对多项式进行拟合并可视化

    本篇文章所讲代码是对2018年全国大学生数学建模比赛A题附件的数据进行拟合,代码如下:

    用户3577892
  • Backtrader量化平台教程(七)Optimizer

    AD:(本人录制的backtrader视频课程,大家多多支持哦~ https://edu.csdn.net/course/detail/9040)       

    钱塘小甲子
  • 【Code】关于 GCN,我有三种写法

    本篇文章主要基于 DGL 框架用三种不同的方式来实现图卷积神经网络。手机看可能不太方便,可以点击阅读原文,移步到知乎上看(但是我忘了加 = =)。

    阿泽 Crz
  • Python使用PyQT制作视频播放器

    最近研究了Python的两个GUI包,Tkinter和PyQT。这两个GUI包的底层分别是Tcl/Tk和QT。相比之下,我觉得PyQT使用起来更加方便,功能也相...

    IT派
  • Day21.Python项目案例

    1、批量修改文件名 2、发送邮件 --- 打卡领取 可配置。 3、批量合成头像图片 4、股票查询程序开发 --- 打...

    DataScience
  • 用 Python 写个坦克大战

    坦克大战是一款策略类的平面射击游戏,于 1985 年由 Namco 游戏公司发布,尽管时至今日已经有了很多衍生类的游戏,但这款游戏仍然受到了相当一部分人的欢迎,...

    纯洁的微笑
  • python写一个简单的俄罗斯方块

    self.color = ['red','orange','yellow','purple','blue','green','pink']

    py3study

扫码关注云+社区

领取腾讯云代金券