1 定义类的几种语法

方式1:类名

在上一章节的学习过程中,我们都使用了这种定义类的语法:

class 类名:
    代码
    ...
例如,使用该方式来定义一个老师类。

# 1.class 类名:
#       pass
class Teacher:
    pass

方式2:类名()

在编写类时,也可以写成如下格式(旧式类):

class 类名():
    代码
    ...
例如,使用该方式来定义一个老师类。

# # 2.class 类名():
# #   pass
class Teacher():
    pass

方式3:类名(object)

此外,还有一种更为常见的定义类的语法(新式类),如下:

class 类名(object):
    代码
    ...
例如,使用该方式来定义一个老师类。

# 3.class 类名(object):
#   pass
class Teacher(object):
    pass

推荐使用第三种类的定义方式

2 python中的继承

2.1 什么是继承

生活中的继承: 一般指的是子女继承父辈的财产。

面向对象中的继承: 指的是多个类之间的所属关系,即子类默认继承父类的属性和方法

作用: 提高代码的复用率, 减少重复代码的书写。

在Python中,继承形式如下:

class 父类名(object):
    ...(省略)
class 子类名(父类名):   # 继承语法
    ...(省略)

例如,Father类有一个默认性别为男,且爱好散步行走,那么,Son类也想要拥有这些属性和行为,该怎么做呢?

"""
继承介绍:
    概述:
        现实中的继承指的是: 子承父业, 编程中的继承指的是: 子类从父类继承过来 属性 和 行为.
    格式:
        class 子类名(父类名):
            pass
    例如:
        class A(B):
            pass
    叫法:
        类A: 子类, 派生类, 扩展类.
        类B: 父类, 基类, 超类.
    好处:
        提高代码的复用性.
    细节:
        所有的类都直接或者间接继承自 object类, 它是所有类的父类, 基类.
"""
# 案例: 定义父类Father类, 属性: 性别=男,  行为: 散步.  定义子类Son, 继承父类, 创建对象, 并调用.
# 1. 定义Father类, 充当父类.
class Father(object):
    # 1.1 定义属性.
    def __init__(self):
        self.gender = '男'

    # 1.2 定义行为
    def walk(self):
        print('饭后走一走, 活到99!')

# 2. 定义子类Son类, 充当子类.
class Son(Father):  # 继承关系 Son -> Father -> object
    pass

# 在main方法中进行测试.
if __name__ == '__main__':
    # 3. 创建子类对象.
    s = Son()

    # 4. 尝试调用 从父类继承过来 的内容(属性 和 行为)
    print(s.gender)
    s.walk()
继承的相关概念:
继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承!
派生:从一个已有的类产生一个新的类,称为派生!
很显然,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的!
父类:也叫作基类,就是指已有被继承的类!
子类:也叫作派生类或扩展类

2.2 单继承

单继承就是一个子类继承一个父类。

语法:

class 类名(父类名):
    代码
    ...

故事1:

一个摊煎饼的老师傅[master],在煎饼果子界摸爬滚打多年,研发了一套精湛的摊煎饼技术, 师父要把这套技术传授给他的唯一的最得意的徒弟[apprentice]。[初始化、无参]

# 单继承指的是: 类只能继承自另外的1个类, 从中继承过来 属性 和 行为.

# 故事1: 一个摊煎饼的老师傅, 在煎饼果子界摸爬滚打多年, 研发了一套精湛的摊煎饼的奇数. 他(老师傅)要传授这个套技术给徒弟. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 2. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(Master):
    pass


# 在main函数中测试.
if __name__ == '__main__':
    # 3. 创建徒弟类对象.
    p = Prentice()
    # 4. 通过徒弟类对象, 调用 从师傅类(父类) 中继承过来的 属性 和 行为.
    print(f'徒弟从师傅继承过来的属性: {p.kongfu}')
    p.make_cake()       # 徒弟从师傅继承过来的 行为.

2.3 多继承

多继承就是:一个类同时继承了多个父类。

语法:

class 类名(父类名1,父类名2,...):  # 多个父类
    代码
    ...

故事2:

小明是个爱学习的好孩子,想学习更多的摊煎饼果子技术,于是,在百度搜索到学校[school],报班来培训学习摊煎饼果子技术。

"""
多继承 细节:
    1. Python中1个类可以继承多个父类, 写法格式为:   class 子类名(父类1, 父类2...):
    2. 如果1个类继承了多个父类, 则该子类可以拥有所有父类的 属性 和 行为.         前提: 父类的私有成员除外.
    3. 如果1个类继承了多个父类, 且多个父类有同名的属性 和 行为, 优先参考 第1个父类的 内容, 这个是根据 MRO规则实现的.
    4. MRO 全称是: Method Resolution Order, 方法解析顺序, 它规定了继承关系中, 属性和行为的 查找顺序, 即: 先找谁, 后找谁.
        调用方式如下:
            类名.__mro__          属性的方式调用.
            类名.mro()            行为(函数)的方式调用.
"""
# 故事2: 小明是个爱学习的好孩子, 想学更多的煎饼果子技术, 于是来到了学校, 报班学习摊煎饼果子技术. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 2. 定义学校类School.
class School(object):
    # 2.1 属性, kongfu = '[摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[摊煎饼果子技术]'

    # 2.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 3. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(School, Master):     # 多继承, 同名属性和行为, 优先参考第1个父类, 即: 从左往右的顺序.
    pass


# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建徒弟类对象.
    p = Prentice()
    # 5. 打印 徒弟类对象 从父类继承过来的 属性.
    print(p.kongfu)
    # 6. 打印 徒弟类对象 从父类继承过来的 行为.
    p.make_cake()

    # 7. 查看MRO规则, method Resolution Order, 方法解析顺序.
    print(Prentice.__mro__)
    print(Prentice.mro())
子类调用父类的同名方法顺序问题

当一个类有多个父类时,默认使用第一个父类的同名属性和方法,可以使用类名**.mro属性**或类名.mro()方法查看调用的先后顺序。

注:MRO(Method Resolution Order):方法解析顺序

print(Tudi.__mro__)
print(Tudi.mro())

2.4 子类重写父类同名方法和属性

重写也叫作覆盖,就是当子类属性或方法与父类的属性或方法名字相同的时候,从父类继承下来的成员可以重新定义!

故事3:

fangge掌握了老师傅的技术后,自己潜心钻研出一套自己的独门配方的全新摊煎饼果子技术。 -老师傅!

"""
方法重写 解释:
    概述:
        子类中出现和父类一模一样的 属性 或者 行为(函数)时, 称为: 方法重写.
    应用场景:
        当子类需要从父类沿袭一些功能, 但是功能主体又有自己独有需求的时候, 就可以考虑使用方法重写了.
"""

# 故事3: 小明掌握了老师傅和学校的技术后, 自己钻研出一套 自己的 独门配方摊煎饼果子法. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 2. 定义学校类School.
class School(object):
    # 2.1 属性, kongfu = '[摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[摊煎饼果子技术]'

    # 2.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 3. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(School, Master):     # 多继承, 同名属性和行为, 优先参考第1个父类, 即: 从左往右的顺序.
    # 3.1 属性
    def __init__(self):
        self.kongfu = '[独创(自研) 摊煎饼果子技术]'

    # 3.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建徒弟类对象.
    p = Prentice()
    # 5. 打印 徒弟类对象 从父类继承过来的 属性.
    print(p.kongfu)
    # 6. 打印 徒弟类对象 从父类继承过来的 行为.
    p.make_cake()

    # 7. 查看MRO规则, method Resolution Order, 方法解析顺序.
    print(Prentice.__mro__)
    print(Prentice.mro())
"""
回顾: 方法重写
    概述:
        子类出现和父类一模一样的方法时, 称为: 方法重写.
    应用场景:
        子类需要沿袭父类的功能, 但是功能主体又有自己独有需求的时候, 就可以考虑使用方法重写了.
"""

# 需求: 定义手机类, 有打电话的功能.  定义新式手机类, 继承手机类, 重写 打电话的行为.
# 1. 定义手机类.
class Phone(object):
    # 打电话的行为
    def call(self, name):
        print(f'----- 拨号: {name} 的手机号')
        print('(拨号方)手机将 信号包发送到 (拨号方)就近的 基站')
        print('基站解析信号包, 获取目标方的位置')
        print('基站通过地下电缆将信号包 发送到 目标方就近的基站')
        print('目标方就近的基站 将信号包发送到 目标方的手机上')
        print('盲音等待, 嘟嘟嘟...')

# 2. 定义新式手机类.
class NewPhone(Phone):
    # 打电话的行为
    def call(self, name):
        # 沿袭父类的功能.
        super().call(name)
        # 功能主体又有自己独有需求的时候.
        print('新式手机的性能, 播放彩铃, 求佛...')

# main方法中完成测试.
if __name__ == '__main__':
    # 3. 创建手机类对象, 打电话.
    p = Phone()
    p.call('刘亦菲')
    print('*' * 20)

    # 4. 创建新手机类对象, 打电话.
    np = NewPhone()
    np.call('胡歌')

2.5 子类调用父类方法

子类中仍想要保留父类的行为,则需要在子类中调用父类方法**.可以**直接使用父类名来进行调用,使用的方法:

父类名.父类方法名()

故事4:

很多顾客都希望能吃到徒弟做出的有自己独立品牌的煎饼果子,也有学校配方技术的煎饼果子味道。

"""
案例: 演示 方法重写后, 子类如何调用父类的 行为(函数)

问题: 重写后, 子类 如何访问 父类的成员?
答案:
    方式1: 父类名.父类方法名(self)            # self本类当前对象的引用.
    方式2: super().父类方法名()

super 关键字介绍:
    概述:
        它代表 本类当前对象 父类的引用.
    简单理解:
        self 代表自己,  super 代表父类.
    细节:
        1. super()只能初始化第1个父类的成员, 所以 super写法 不适用于 多继承, 更适用于 单继承.
        2. 在单继承关系中, 可以把 super().父类方法名(self) 简写成 super().父类方法名()
        3. 多继承关系中, 如果想精准的初始化某个父类的成员, 要通过 父类名.父类方法名(self) 的方式实现.
"""


# 故事4: 很多顾客都希望吃到 徒弟自研的煎饼果子, 也有 学校配方的煎饼果子味道. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 2. 定义学校类School.
class School(object):
    # 2.1 属性, kongfu = '[摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[摊煎饼果子技术]'

    # 2.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 3. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(School, Master):     # 多继承, 同名属性和行为, 优先参考第1个父类, 即: 从左往右的顺序.
    # 3.1 属性
    def __init__(self):
        self.kongfu = '[独创(自研) 摊煎饼果子技术]'

    # 3.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')

    # 3.3 行为, make_master_cake(),  从老师傅继承过来的 煎饼果子配方.
    def make_master_cake(self):
        # 子类调用父类行为, 方式1: 父类名.父类方法名(self)
        # 初始化父类的 属性, 即: 调用父类的 __init__()函数
        Master.__init__(self)   # 等价于做了: self.kongfu = '[古法摊煎饼果子技术]'
        Master.make_cake(self)  # 调用父类的行为.

    # 3.4 行为, make_school_cake(),  从学校继承过来的 煎饼果子配方.
    def make_school_cake(self):
        # 子类调用父类行为, 方式1: 父类名.父类方法名(self)
        # 初始化父类的 属性, 即: 调用父类的 __init__()函数
        School.__init__(self)  # 等价于做了: self.kongfu = '[摊煎饼果子技术]'
        School.make_cake(self)  # 调用父类的行为.

    # 3.5 行为, make_old_cake(),  从 父类继承过来的 煎饼果子配方.
    def make_old_cake(self):
        # 子类调用父类行为, 方式2: super().父类方法名()
        super().__init__()  # 初始化父类的成员.
        super().make_cake()


# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建徒弟类对象.
    p = Prentice()
    # 自研的.
    p.make_cake()           # 自研

    # 5. 打印 徒弟类对象 从 Master(老师父类)继承过来的 行为.
    # p.make_master_cake()    # 古法

    # 6. 打印 徒弟类对象 从 School(学校类)继承过来的 行为.
    # p.make_school_cake()    # 学校

    # 7. 打印 徒弟类对象 从 父类继承过来的 行为. 即: 旧的煎饼果子配方.
    p.make_old_cake()       #


使用父类名进行初始化之后在调用相应的方法

2.6 super方法调用父类方法

使用super()调用父类方法,使用的方法:

super().父类方法名()

故事4:

很多顾客都希望能吃到徒弟做出的有自己独立品牌的煎饼果子,也有学校配方技术的煎饼果子味道。

# 1.定义师傅类
class Master(object):
    # 1.1 属性
    def __init__(self):
        self.kofu = '[传统方法]'
​
    # 1.2 方法
    def make_cake(self):
        print(f'使用{self.kofu}摊煎饼')
​
​
# 2.定义学校类
class School(object):
    # 1.1 属性
    def __init__(self):
        self.kofu = '[方法]'
​
    # 1.2 方法
    def make_cake(self):
        print(f'使用{self.kofu}摊煎饼')
​
​
# 3.定义徒弟类
class Tudi(Heima):
    # 1.1 属性
    def __init__(self):
        self.kofu = '[独创技术]'
​
    # 1.2 方法
    def make_cake(self):
        print(f'使用{self.kofu}摊煎饼')
​
    # 1.4 使用学校技术
    def schol_make_cake(self):
        super().__init__()
        super().make_cake()
​
​
# 3.实例化
xiaoming = Tudi()
print(xiaoming.kofu)
xiaoming.make_cake()
# xiaoming.master_make_cake()
xiaoming.schol_make_cake()
​
# print(Tudi.__mro__)
# print(Tudi.mro())
注意:使⽤super() 可以⾃动查找⽗类,适合单继承使⽤,多继承不建议使用

2.7 多层继承

故事5:

N年后,小明老了,想要把"有自己的独立品牌,也有学校配方技术的煎饼果子味道"的所有技术传授给自己的徒弟。

"""
案例: 演示 多层继承

问题: 重写后, 子类 如何访问 父类的成员?
答案:
    方式1: 父类名.父类方法名(self)            # self本类当前对象的引用.
    方式2: super().父类方法名()

细节:
    1. 你要分清楚 多层继承 和 多继承.
    2. 多继承: 1个类有多个父类.
    3. 多层继承: 继承的传递性.
"""

# 故事4: N年后, 小明老了, 想把自己独创的, 学校配方, 古法配方的煎饼果子技术, 都传给自己的徒弟. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')

# 2. 定义学校类School.
class School(object):
    # 2.1 属性, kongfu = '[摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[摊煎饼果子技术]'

    # 2.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')

# 3. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(School, Master):     # 多继承, 同名属性和行为, 优先参考第1个父类, 即: 从左往右的顺序.
    # 3.1 属性
    def __init__(self):
        self.kongfu = '[独创(自研) 摊煎饼果子技术]'

    # 3.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')

    # 3.3 行为, make_master_cake(),  从老师傅继承过来的 煎饼果子配方.
    def make_master_cake(self):
        # 子类调用父类行为, 方式1: 父类名.父类方法名(self)
        # 初始化父类的 属性, 即: 调用父类的 __init__()函数
        Master.__init__(self)   # 等价于做了: self.kongfu = '[古法摊煎饼果子技术]'
        Master.make_cake(self)  # 调用父类的行为.

    # 3.4 行为, make_school_cake(),  从学校继承过来的 煎饼果子配方.
    def make_school_cake(self):
        # 子类调用父类行为, 方式1: 父类名.父类方法名(self)
        # 初始化父类的 属性, 即: 调用父类的 __init__()函数
        School.__init__(self)  # 等价于做了: self.kongfu = '[摊煎饼果子技术]'
        School.make_cake(self)  # 调用父类的行为.

    # 3.5 行为, make_old_cake(),  从 父类继承过来的 煎饼果子配方.
    def make_old_cake(self):
        # 子类调用父类行为, 方式2: super().父类方法名()
        super().__init__()  # 初始化父类的成员.
        super().make_cake()

# 4. 定义徒孙类, 继承子: 徒弟类.
class TuSun(Prentice):  # 继承关系:  TuSun -> Prentice -> School, Master -> object
    pass

# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建 徒孙类 对象, 调用: 从父类, 父类的父类 中继承过来的内容.
    ts = TuSun()
    ts.make_cake()          # 自研
    ts.make_school_cake()   # 
    ts.make_master_cake()   # 古法


  • 继承:子类拥有父类的属性和方法

  • 单继承:一个子类有一个父类

  • 多继承:一个子类有多个父类

  • mro资源顺序

    子类.mro()
    子类.__mro__ 
    尝试去追踪底层代码
  • 子类重新父类的属性和方法

    • 优先使用子类的属性方法

  • 需要显示的调用父类的属性和方法

    • 父类.方法名(self)

    • Super()适合单继承

      • Super()按照mro资源表,去查找父类的调用顺序;不是按照我们的人为的继承

3 python中的封装


3.1 什么是封装

在软件编程中,将属性和方法书写到类的里面的操作即为封装,封装可以为属性和方法添加私有权限

3.2 定义和获取私有属性

在Python中,可以为属性设置私有权限,即设置某个属性不继承给子类。

设置私有权限的方式:在属性名前面加上两个下划线 __,格式:

self.__属性名

故事6:

fangge把技术传承给徒弟的同时,不想把自己的私房钱($5000000)继承给徒弟,这时就要为这个属性设置私有权限。

"""
封装 解释:
    概述:
        在Python中, 我们定义函数的动作, 叫: 封装.
        在Python中, 我们定义类, 类中有属性 和 行为, 这个动作: 也叫封装.
    专业版话述:
        封装就是隐藏对象的 属性 和 实现细节, 仅对外提供公共的访问方式.
    问1: 怎么隐藏?
    答案: 私有化的方式, __ 注意, 这里是 两个下划线.

    问2: 公共的访问方式是什么?
    答案: 就是自己提供的对外访问内部成员的接口(方式), 例如: get_xxx(), set_xxx()

    问3: 目前我们所学的内容, 子类可以从父类继承过来所有的 属性 和 行为, 那如果父类的某些 属性和行为不让子类继承, 如何实现呢?
    答案: 可以通过 私有化解决.

私有化介绍:
    概述:
        被它修饰的内容, 只能在 本类中 直接访问.
    格式:
        __属性名           注意: 这里是 两个 _
        __函数名()
    特点:
        私有化成员, 子类无法继承, 只能本类使用.  如果子类想访问, 必须通过提供的 公共的访问方式(接口)
"""

# 故事6: 小明把技术传给徒弟的同时, 不想把自己的私房钱继承给徒弟, 请用所学, 模拟该知识.

# 1. 定义 徒弟类.
class Prentice(object):
    # 1.1 初始化属性.
    def __init__(self):
        self.kongfu = '[自研 摊煎饼果子技术]'
        self.__money = 500        # 徒弟的私房钱, 私有化了.

    # 1.2 行为, 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼!')

    # 1.3 定义公共的访问方式(接口), 查看 私房钱.
    def get_money(self):
        return self.__money

    # 1.4 定义公共的访问方式(接口), 修改 私房钱.
    def set_money(self, money):
        self.__money = money


# 2. 定义 徒孙类, 继承子 徒弟类.
class TuSun(Prentice):
    pass

# main方法中测试.
if __name__ == '__main__':
    # 3. 创建 徒孙类对象.
    ts = TuSun()
    # 4. 尝试访问 从父类(徒弟类)中继承过来的内容.
    print(ts.kongfu)
    ts.make_cake()

    # 5. 尝试访问 父类(徒弟类)的私房钱.
    # print(f'(徒孙) 看到了 (徒弟)的私房钱: {ts.money}')
    # print(f'(徒孙) 看到了 (徒弟)的私房钱: {ts.__money}')
    print(f'(徒孙) 看到了 (徒弟)的私房钱: {ts.get_money()}')

    # 修改 徒弟的私房钱.
    ts.set_money(666666666)

    print(f'(徒孙) 看到了 (徒弟)的私房钱: {ts.get_money()}')


3.3 定义和获取私有方法

在Python中,可以为方法设置私有权限,即设置某个方法不继承给子类。

设置私有权限的方式:在方法名前面加上两个下划线 __,格式:

def __方法名(self):
     ...

故事7:

小明把煎饼果子技术传承给徒弟的同时,不想把自己的独创配方制作过程继承给徒弟,这时就要为制作独创配方这个方法设置私有权限。

"""
封装 解释:
    概述:
        在Python中, 我们定义函数的动作, 叫: 封装.
        在Python中, 我们定义类, 类中有属性 和 行为, 这个动作: 也叫封装.
    专业版话述:
        封装就是隐藏对象的 属性 和 实现细节, 仅对外提供公共的访问方式.
    问1: 怎么隐藏?
    答案: 私有化的方式, __ 注意, 这里是 两个下划线.

    问2: 公共的访问方式是什么?
    答案: 就是自己提供的对外访问内部成员的接口(方式), 例如: get_xxx(), set_xxx()

    问3: 目前我们所学的内容, 子类可以从父类继承过来所有的 属性 和 行为, 那如果父类的某些 属性和行为不让子类继承, 如何实现呢?
    答案: 可以通过 私有化解决.

私有化介绍:
    概述:
        被它修饰的内容, 只能在 本类中 直接访问.
    格式:
        __属性名           注意: 这里是 两个 _
        __函数名()
    特点:
        私有化成员, 子类无法继承, 只能本类使用.  如果子类想访问, 必须通过提供的 公共的访问方式(接口)
"""

# 故事7: 小明把煎饼果子技术传承给徒弟的同时, 不想把具体的制作过程继承给徒弟, 请用所学, 模拟该知识.

# 1. 定义徒弟类.
class Prentice(object):
    # 1.1 初始化属性.
    def __init__(self):
        self.kongfu = '[自研摊煎饼果子技术]'
        self.__money = 500      # 私有化属性

    # 1.2 行为, 摊煎饼.      私有方法, 子类无法继承.
    def __make_cake(self):
        print(f'采用 {self.kongfu} 摊煎饼!')

    # 1.3 提供针对于 __money 私有属性的, 公共的访问方式.
    def get_money(self):
        return self.__money

    def set_money(self, money):
        self.__money = money

    # 1.4 提供个公共的函数, 用于访问: 摊煎饼 这个私有的方法.
    def make(self):
        self.__make_cake()

# 2. 定义徒孙类.
class TuSun(Prentice):
    pass

# 在main方法中测试.
if __name__ == '__main__':
    # 3. 创建徒孙类对象.
    ts = TuSun()

    # 4. 尝试访问从父类继承过来的内容.
    print(ts.kongfu)        # [自研摊煎饼果子技术], 非私有属性.
    # print(ts.money)         # 报错.
    print(f'徒弟的私房钱为: {ts.get_money()}')

    # 修改私房钱.
    ts.set_money(1000000)
    print(f'徒弟的私房钱为: {ts.get_money()}')
    print('-' * 20)

    # 5. 尝试调用从父类继承过来的方法.
    # ts.make_cake()        报错, 父类私有函数不能直接访问.
    ts.make()

私有权限 :

  • 私有属性

    • 只能在类内部访问

    • 定义公有方法获取get_xxx或设置set_xxx私有属性值

  • 私有方法

    • 只能在类内部访问

    • 定义公有方法调用私有方法

4 python中的多态


4.1 多态定义

多态,指的是:多种状态。比如:同样一个函数在不同的场景下有不同的状态。

"""
多态介绍:
    概述:
        多态指的是同一个事物在不同场景下表现出来的不同形态, 状态.
        Python中的多态指的是, 同一个函数, 传入不同的对象, 会实现不同的结果.
    多态的前提条件:
        1. 要有继承关系.
        2. 要有方法重写.
        3. 要有父类引用指向子类对象.            an:Animal = Dog()   an:Animal = Cat()      狗是动物, 猫是动物
    好处:
        提高代码的可维护性. 实现: 1个函数, 多种效果.
    应用场景:
        父类型充当函数形参的类型, 这样可以接受其任意的子类对象, 实现: 传入什么(子类)对象, 就调用其对应的功能.

    细节:
        有人说, Python中的多态其实是"伪多态", 因为 函数的形参类型, 并不能严格限制必须传入该类型或者其子类型的对象,
        其实传入其它类型的对象也是可以的.
"""


# 案例: 动物类案例.
# 1. 定义动物类, 有 speak()函数, 表示: 叫.
class Animal(object):
    def speak(self):
        print('动物会叫!')


# 2. 定义狗类, 继承自动物类, 重写: speak()函数.
class Dog(Animal):
    def speak(self):
        print('狗 汪汪汪 叫!')


# 3. 定义猫类, 继承自动物类, 重写: speak()函数.
class Cat(Animal):
    def speak(self):
        print('猫 喵喵喵 叫!')

# 扩展: 定义1个手机类.
class Phone(object):
    def speak(self):
        print('我像只鱼儿在你的荷塘, 只为寻找那...')


# 4. 定义函数 make_noise(动物类对象), 接收动物对象, 实现: 传入什么动物, 就怎么叫.
def make_noise(an: Animal):  # an:Animal 意思是: an "必须" 是Animal类的对象 或者 其子类对象.
    # 接收动物对象, 实现: 传入什么动物, 就有: 对应的叫声.
    an.speak()      # 猫对象.speak(),     狗对象.speak()


# 在main方法中测试.
if __name__ == '__main__':
    # 5. 分别创建猫类, 狗类对象.
    c = Cat()
    d = Dog()

    # 6. 调用 make_noise()函数, 实现: 多态.   传入狗对象, 就: 汪汪汪叫,  传入猫对象, 就: 喵喵喵叫.
    make_noise(c)       # 猫 喵喵喵 叫!
    make_noise(d)       # 猫 喵喵喵 叫!
    print('-' * 20)

    # 7. 演示 "伪多态"
    p = Phone()
    make_noise(p)

    # 扩展: 看变量属于什么类型.
    print(type(p))                  # type()函数, 看对象属于什么类型.
    print(isinstance(p, Phone))     # isinstance(对象名, 类型) 判断对象是否是对应的类型.
    print(isinstance(p, Animal))    # isinstance(对象名, 类型) 判断对象是否是对应的类型.
    print(isinstance(c, Animal))    # True

4.2 多态的条件

1、有继承 (定义父类、定义子类,子类继承父类)
2、函数重写 (子类重写父类的函数)
3、父类引用指向子类对象 (子类对象传给父类对象调用者)
"""
需求:
    构建对象对战平台object_play
        1 英雄一代战机(战斗力60)与敌军战机(战斗力70)对抗。英雄1代战机失败!
        2 卧薪尝胆,英雄二代战机(战斗力80)出场!,战胜敌军战机!
        3 对象对战平台object_play, 代码不发生变化的情况下, 完成多次战斗.

分析:
    抽象战机类 HeroFighter  AdvHeroFighter;敌机EnemyFighter;
    构建对象战斗平台, 使用多态实现

"""


# 1. 定义英雄1代机 战斗力为: 60
class HeroFighter(object):
    def power(self):
        return 60  # 攻击力: 60


# 2. 定义英雄2代机 战斗力为: 80,  继承自: 英雄1代机.
class AdvHeroFighter(HeroFighter):  # advanced: 进阶...
    def power(self):
        return 80  # 攻击力: 80


# 3. 定义敌机, 战斗力为: 70
class EnemyFighter(object):
    def power(self):
        return 70  # 攻击力: 70


# 4. 构建对象对战平台 object_play(英雄机, 敌机)
def object_play(hf: HeroFighter, ef: EnemyFighter):
    """
    构建对象对战平台, 模拟: 英雄机 和 敌机对战.
    :param hf: 英雄机
    :param ef: 敌机
    :return: 无
    """
    if hf.power() >= ef.power():
        print('英雄机 获胜!')
    else:
        print('敌机 获胜!')

# 在main方法中测试.
if __name__ == '__main__':
    # 5. 分别创建英雄1代机, 2代机, 敌机对象.
    hf = HeroFighter()
    hf2 = AdvHeroFighter()
    ef = EnemyFighter()

    # 6. 具体的对战过程.
    # 英雄1代机 和 敌机
    object_play(hf, ef)         # 敌机 获胜!
    # 英雄2代机 和 敌机
    object_play(hf2, ef)        # 英雄机 获胜!

4.3 多态的好处

1、在不改变框架代码的情况下,通过多态语法轻松的实现模块和模块之间的解耦合;实现了软件系统的可拓展
​
2、对解耦合的大白话解释:搭建的平台函数def object_play(herofighter:HeroFighter, enemyfighter:EnemyFighter) 相当于任务的调用者;子类、孙子类重写父类的函数,相当于子任务;相当于任务的调用者和任务的编写者进行了解耦合
​
3、对可拓展的大白话解释: 搭建的平台函数def object_play(herofighter:HeroFighter, enemyfighter:EnemyFighter),在不做任何修改的情况下,可以调用后来人写的代码
​
4、对“继承和多态对照理解”大白话解释:
    继承相当于:孩子可以复用老爹的东西。
    多态相当于:老爹框架,不做任何修改的情况下,可以可拓展的使用后来人(孩子)写的东西。
​

4.4 抽象类(接口)

"""
抽象类介绍:
    概述:
        在Python中, 抽象类也叫接口, 指的是: 有抽象方法的类, 就叫抽象类.
    抽象方法:
        没有方法体的方法, 即: 空实现的方法, 用pass修饰, 就叫: 抽象方法.
        例如:
            def get_sum():
                pass
    用法:
        抽象类一般充当 父类, 即: 制定整个继承体系的 标准(规范)
        具体的体现, 实现交由 子类来完成.
"""
# 需求: 定义空调类(AC), 制定标准: 制冷, 制热, 左右摆风.   两个厂商(美的, 格力)根据标准, 制作空调.

# 1. 定义空调类, 制定标准: 制冷, 制热, 左右摆风
class AC(object):
    # 抽象方法.
    # 制冷
    def cool_wind(self):
        pass

    # 制热
    def hot_wind(self):
        pass

    # 左右摆风
    def swing_l_r(self):
        pass

# 2. 美的空调.
class Media(AC):
    # 制冷
    def cool_wind(self):
        print('美的空调 核心制冷技术 制作冷风')

    # 制热
    def hot_wind(self):
        print('美的空调 核心制热技术 制作热风')

    # 左右摆风
    def swing_l_r(self):
        print('美的空调 遥控器设置 左右摆风')

# 3. 格力空调.
class Gree(AC):
    # 制冷
    def cool_wind(self):
        print('格力空调 核心制冷技术 制作冷风')

    # 制热
    def hot_wind(self):
        print('格力空调 太阳能技术 制作热风')

    # 左右摆风
    def swing_l_r(self):
        print('格力空调 AI控制 左右摆风')


# 测试代码
if __name__ == '__main__':
    # 4. 测试 美的空调.
    m = Media()
    m.cool_wind()
    m.hot_wind()
    m.swing_l_r()
    print('-' * 20)

    # 5. 测试 格力空调.
    g = Gree()
    g.cool_wind()
    g.hot_wind()
    g.swing_l_r()

5 面向对象的其他特性


5.1 属性

对于属性,调用时有两种方式,如下:

self.对象属性名  # 类内部
或:

对象名.对象属性名   # 类外部

例如,编写一个手机类,有品牌[类内部]、颜色[类外部]属性。

# 1.定义类
class Phone(object):
    def __init__(self):
        self.brand = '华为'
​
# 2.实例化对象
phone = Phone()
phone.color = 'purple'
​
print(phone.brand)
print(phone.color)

5.2 类属性

所谓类属性,指的就是类所拥有的属性,它被共享于整个类中(即都可以直接调用),格式如下:

类名.类属性名    # 推荐使用
或:

对象名.类属性名

例如,在People类中定义一个名为count的类属性。

# 1.定义类
class Person(object):
    count = 1
    __height = 165
​
​
# 2.获取属性值:类名.类属性
print(Person.count)
# p = Person()
# print(p.count)
"""
属性 介绍:
    概述:
        属性 = 名词, 就是描述事物的外在特征的, 例如: 姓名, 年龄, 性别...
    分类:
        对象属性: 属于每个对象的属性, 即: A对象修改了他的属性值, 不会影响B对象的属性值.
        类属性:   它是属于该类的所有对象 所共享的, 即: A对象修改了类属性, 则B对象用的是 修改后的属性值.

对象属性 相关格式:
    定义格式:
        类外: 对象名.属性名 = 属性值
        类内: self.属性名 = 属性值
    获取属性值的格式:
        类外: 对象名.属性名
        类内: self.属性名

类属性:
    定义格式:
        定义在类中, 函数外的 变量, 就叫: 类变量, 它能被该类下所有的对象所共享.
    调用格式:
        方式1: 类名.类属性名            # 推荐使用.
        方式2: 对象名.类属性名          # 可以用, 但是不推荐.

细节: 什么时候定义类变量, 什么时候定义对象变量?
    如果1个变量是被该类下所有的对象所共享的, 就考虑定义成: 类变量(类属性), 否则定义成: 对象变量(对象属性).
"""

# 需求: 定义学生类, 有 name属性,  teacher_name属性, 创建学生类对象, 并测试. 请用所学, 模拟该知识.

# 1. 定义学生类.
class Student(object):
    # 1.1 老师名字.
    teacher_name = '夯哥'            # 类属性, 每个对象所共享.

    # 1.2 name属性, 每个学生的名字都不一样, 所以定义成: 对象属性.
    def __init__(self):
        self.name = '张三'            # 对象属性.


# 在main方法中调用
if __name__ == '__main__':
    # 2. 创建学生类对象.
    s1 = Student()
    s2 = Student()

    # 3. 打印对象属性值.
    print(f's1对象的 对象属性: {s1.name}')     # '张三'
    print(f's2对象的 对象属性: {s2.name}')     # '张三'
    # 4. 修改s1的对象属性值.
    s1.name = '王五'
    # 5. 重新打印对象属性值.
    print(f's1对象的 对象属性: {s1.name}')     # '王五'
    print(f's2对象的 对象属性: {s2.name}')     # '张三'

    # 6. 打印 类属性值.
    print(f's1对象的 类属性: {s1.teacher_name}')  # '夯哥'
    print(f's2对象的 类属性: {s2.teacher_name}')  # '夯哥'
    # 7. 修改s1的类属性值.
    Student.teacher_name = '梅婷'
    # 8. 重新打印 类属性值.
    print(f's1对象的 类属性: {s1.teacher_name}')  # '梅婷'
    print(f's2对象的 类属性: {s2.teacher_name}')  # '梅婷'

    # 9. 类属性, 推荐使用 类名.的方式调用.
    print(Student.teacher_name)

5.3 类方法

所谓类方法,指的是类所拥有的方法,并需要使用[装饰器]@classmethod来标识其为类方法

同时一定要注意的是对于类方法的第一个参数必须是类对象,通常以cls作为第一个参数名,格式如下:

@classmethod
def 类方法名(cls):
    ...
调用时,格式如下:

类名.类方法名()    # 推荐使用
或:

对象名.类方法名()

例如,狗狗都喜欢吃骨头。

# 1.定义类
class Dog(object):
    __num = 6
    # 类方法
    @classmethod
    def eat(cls):
        print(cls.__num)
        print('小狗喜欢吃骨头')
​
# 2.调用类方法
Dog.eat()
# dog = Dog()
# dog.eat()

扩展:语法

类方法一般和类属性配合使用,尤其是使用私有类属性时。

6.4 静态方法

静态方法需要通过装饰器@staticmethod来来标识其为静态方法,且静态方法不需要多定义参数,

格式如下:

@staticmethod
def 静态方法名():
    ...
调用时,格式如下:

类名.静态方法名    # 推荐使用
或:

对象名.静态方法名
"""
类方法 和 静态方法详解:
    概述:
        类方法:
            1. 第1个参数必须是 当前类的对象, 一般用 cls当做变量名(即: class)
            2. 类方法必须通过 @classmethod 来修饰.
            3. 类方法是属于 类的方法, 能被该类下所有的对象所共享.
            4. 可以通过 类名. 或者 对象名. 的方式调用, 推荐: 前者.
        静态方法:
            1. 静态方法没有参数的硬性要求, 可以1个参数都不传.
            2. 静态方法必须通过 @staticmethod 来修饰.
            3. 类方法是属于 类的方法, 能被该类下所有的对象所共享.
            4. 可以通过 类名. 或者 对象名. 的方式调用, 推荐: 前者.

    区别:
        类方法 和 静态方法的区别: 要不要传参, 即: 第一个参数是写 还是 不写, 再简单点说: 是否需要使用 该类的对象, 用就定义成 类方法, 不用就定义成 静态方法.

    细节:

"""

# 需求: 定义学生类, 有 类属性 teacher_name, 对象属性: name.  类方法, 静态方法, 在测试类中进行测试.

# 1. 定义学生类, 有 类属性 teacher_name, 对象属性: name.  类方法, 静态方法
class Student(object):
    # 1.1 老师名字.
    teacher_name = '夯哥'  # 类属性, 每个对象所共享.

    # 1.2 name属性, 每个学生的名字都不一样, 所以定义成: 对象属性.
    def __init__(self):
        self.name = '张三'  # 对象属性.

    # 1.3 定义静态方法, 访问: teacher_name 这个类变量.
    @staticmethod
    def method01():
        print(Student.teacher_name)  # 类名. 的方式, 访问: 类变量.

    # 1.4 定义类方法, 访问: teacher_name 这个类变量.
    @classmethod
    def method02(cls):      # 这里的cls 是 class的意思.
        print(cls.teacher_name)  # 类名. 的方式, 访问: 类变量.


if __name__ == '__main__':
    # 2. 创建学生对象.
    s = Student()
    s.method01()        # 对象名. 的方式 访问静态方法, 可以, 但是不推荐.

    Student.method01()  # 类名. 的方式, 访问静态方法, 推荐.
    Student.method02()  # 类名. 的方式, 访问静态方法, 推荐.

【上一篇】Python进阶】8.面向对象基础

【下一篇】【Python进阶】10.深拷贝&浅拷贝

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐