【后端】Python 体系(四)

面向对象

四、面向对象封装案例

1. 封装

  1. 封装 是面向对象编程的一大特点
  2. 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中
  3. 外界 使用 类 创建 对象,然后 让对象调用方法
  4. 对象方法的细节 都被 封装 在 类的内部

2. 小明爱跑步

  • 需求

    • 小明 体重 75.0 公斤
    • 小明每次 跑步 会减肥 0.5 公斤
    • 小明每次 吃东西 体重增加 1 公斤
      在这里插入图片描述
  • 提示:在 对象的方法内部,是可以 直接访问对象的属性 的!

class Person:
    """人类"""

    def __init__(self, name, weight):

        self.name = name
        self.weight = weight

    def __str__(self):

        return "我的名字叫 %s 体重 %.2f 公斤" % (self.name, self.weight)

    def run(self):
        """跑步"""

        print("%s 爱跑步,跑步锻炼身体" % self.name)
        self.weight -= 0.5

    def eat(self):
        """吃东西"""

        print("%s 是吃货,吃完这顿再减肥" % self.name)
        self.weight += 1


xiaoming = Person("小明", 75)

xiaoming.run()
xiaoming.eat()
xiaoming.eat()

print(xiaoming)

3. 摆放家具

  • 需求
    • 房子(House) 有 户型、总面积 和 家具名称列表
      • 新房子没有任何的家具
    • 家具(HouseItem) 有 名字 和 占地面积,其中
      • 席梦思(bed) 占地 4 平米
      • 衣柜(chest) 占地 2 平米
      • 餐桌(table) 占地 1.5 平米
    • 将以上三件 家具 添加 到 房子 中
    • 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
      在这里插入图片描述
  • 剩余面积
  1. 在创建房子对象时,定义一个 剩余面积的属性,初始值和总面积相等
  2. 当调用 add_item 方法,向房间 添加家具 时,让 剩余面积 -= 家具面积
  • 思考:应该先开发哪一个类?

  • 答案 —— 家具类

  1. 家具简单
  2. 房子要使用到家具,被使用的类,通常应该先开发
3.1 创建家具
class HouseItem:

    def __init__(self, name, area):
        """

        :param name: 家具名称
        :param area: 占地面积
        """
        self.name = name
        self.area = area

    def __str__(self):
        return "[%s] 占地面积 %.2f" % (self.name, self.area)


# 1. 创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)

print(bed)
print(chest)
print(table)
  • 小结
  1. 创建了一个 家具类,使用到 initstr 两个内置方法
  2. 使用 家具类 创建了 三个家具对象,并且 输出家具信息
3.2 创建房间
class House:

    def __init__(self, house_type, area):
        """

        :param house_type: 户型
        :param area: 总面积
        """
        self.house_type = house_type
        self.area = area
        
        # 剩余面积默认和总面积一致
        self.free_area = area
        # 默认没有任何的家具
        self.item_list = []

    def __str__(self):

        # Python 能够自动的将一对括号内部的代码连接在一起
        return ("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
                % (self.house_type, self.area,
                   self.free_area, self.item_list))

    def add_item(self, item):

        print("要添加 %s" % item)

...

# 2. 创建房子对象
my_home = House("两室一厅", 60)

my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)

print(my_home)
  • 小结
  1. 创建了一个 房子类,使用到 __init__ 和 __str__ 两个内置方法
  2. 准备了一个 add_item 方法 准备添加家具
  3. 使用 房子类 创建了 一个房子对象
  4. 让 房子对象 调用了三次 add_item 方法,将 三件家具 以实参传递到 add_item 内部
3.3 添加家具
  • 需求
    • 1> 判断 家具的面积 是否 超过剩余面积,如果超过,提示不能添加这件家具
    • 2> 将 家具的名称 追加到 家具名称列表 中
    • 3> 用 房子的剩余面积 - 家具面积
    def add_item(self, item):

        print("要添加 %s" % item)
        # 1. 判断家具面积是否大于剩余面积
        if item.area > self.free_area:
            print("%s 的面积太大,不能添加到房子中" % item.name)

            return

        # 2. 将家具的名称追加到名称列表中
        self.item_list.append(item.name)

        # 3. 计算剩余面积
        self.free_area -= item.area
3.4 小结
  • 主程序只负责创建 房子 对象和 家具 对象
  • 让 房子 对象调用 add_item 方法 将家具添加到房子中
  • 面积计算、剩余面积、家具列表 等处理都被 封装 到 房子类的内部

五、面向对象封装案例 II

1. 士兵突击

  • 需求
  1. 士兵 许三多 有一把 AK47
  2. 士兵 可以 开火
  3. 枪 能够 发射 子弹
  4. 枪 装填 装填子弹 —— 增加子弹数量
    在这里插入图片描述
1.1 开发枪类
  • shoot 方法需求
    • 1> 判断是否有子弹,没有子弹无法射击
    • 2> 使用 print 提示射击,并且输出子弹数量
class Gun:

    def __init__(self, model):

        # 枪的型号
        self.model = model
        # 子弹数量
        self.bullet_count = 0

    def add_bullet(self, count):

        self.bullet_count += count

    def shoot(self):

        # 判断是否还有子弹
        if self.bullet_count <= 0:
            print("没有子弹了...")

            return

        # 发射一颗子弹
        self.bullet_count -= 1
        
        print("%s 发射子弹[%d]..." % (self.model, self.bullet_count))

# 创建枪对象
ak47 = Gun("ak47")
ak47.add_bullet(50)
ak47.shoot()
1.2 开发士兵类
  • 假设:每一个新兵 都 没有枪
  • 定义没有初始值的属性
  • 在定义属性时,如果 不知道设置什么初始值,可以设置为 None
    • None 关键字 表示 什么都没有
    • 表示一个 空对象,没有方法和属性,是一个特殊的常量
    • 可以将 None 赋值给任何一个变量
  • fire 方法需求
    • 1> 判断是否有枪,没有枪没法冲锋
    • 2> 喊一声口号
    • 3> 装填子弹
    • 4> 射击
class Soldier:

    def __init__(self, name):

        # 姓名
        self.name = name
        # 枪,士兵初始没有枪 None 关键字表示什么都没有
        self.gun = None

    def fire(self):

        # 1. 判断士兵是否有枪
        if self.gun is None:
            print("[%s] 还没有枪..." % self.name)

            return

        # 2. 高喊口号
        print("冲啊...[%s]" % self.name)

        # 3. 让枪装填子弹
        self.gun.add_bullet(50)

        # 4. 让枪发射子弹
        self.gun.shoot()
  • 小结
  1. 创建了一个 士兵类,使用到 __init__ 内置方法
  2. 在定义属性时,如果 不知道设置什么初始值,可以设置为 None
  3. 在 封装的 方法内部,还可以让 自己的 使用其他类创建的对象属性 调用已经 封装好的方法

2. 身份运算符

  • 身份运算符用于 比较 两个对象的 内存地址 是否一致 —— 是否是对同一个对象的引用
  • 在 Python 中针对 None 比较时,建议使用 is 判断
运算符描述实例
isis 是判断两个标识符是不是引用同一个对象x is y,类似 id(x) == id(y)
is notis not 是判断两个标识符是不是引用不同对象x is not y,类似 id(a) != id(b)
  • is 与 == 区别:
    • is 用于判断 两个变量 引用对象是否为同一个
    • == 用于判断 引用变量的值 是否相等
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a 
False
>>> b == a
True

六、私有属性和私有方法

1. 应用场景及定义方式

  • 应用场景
    • 在实际开发中,对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到
    • 私有属性 就是 对象 不希望公开的 属性
    • 私有方法 就是 对象 不希望公开的 方法
  • 定义方式
    • 在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线,定义的就是 私有 属性或方法
      在这里插入图片描述
class Women:

    def __init__(self, name):

        self.name = name
        # 不要问女生的年龄
        self.__age = 18

    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiaofang = Women("小芳")
# 私有属性,外部不能直接访问
# print(xiaofang.__age)

# 私有方法,外部不能直接调用
# xiaofang.__secret()

2. 伪私有属性和私有方法(科普)

  • 提示:在日常开发中,不要使用这种方式,访问对象的 私有属性 或 私有方法

  • Python 中,并没有 真正意义 的 私有

    • 在给 属性、方法 命名时,实际是对 名称 做了一些特殊处理,使得外界无法访问到
    • 处理方式:在 名称 前面加上 _类名 => _类名__名称
print(xiaofang._Women__age)	# 18
xiaofang._Women__secret() # 我的年龄是 18

七、单例

1. 单例设计模式

  • 设计模式

    • 设计模式 是 前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对 某一特定问题 的成熟的解决方案
    • 使用 设计模式 是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
  • 单例设计模式

    • 目的 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例
    • 每一次执行 类名() 返回的对象,内存地址是相同的
  • 单例设计模式的应用场景

    • 音乐播放 对象
    • 回收站 对象
    • 打印机 对象

2. __new__ 方法

  • 使用 类名() 创建对象时,Python 的解释器 首先 会 调用 __new__ 方法为对象 分配空间
  • __new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:

    ⅰ. 在内存中为对象 分配空间

    ⅱ. 返回 对象的引用
  • Python 的解释器获得对象的 引用 后,将引用作为 第一个参数,传递给\ _init_ 方法
  • 重写 new 方法 的代码非常固定!
  • 重写 new 方法 一定要 return super().__new__(cls)
  • 否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法
  • 注意:new 是一个静态方法,在调用时需要 主动传递 cls 参数
    在这里插入图片描述
class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        # 如果不返回任何结果,
        return super().__new__(cls)

    def __init__(self):
        print("初始化音乐播放对象")

player = MusicPlayer()

print(player)

3. Python 中的单例

  • 单例 —— 让 类 创建的对象,在系统中 只有 唯一的一个实例
    a. 定义一个 类属性,初始值是 None,用于记录 单例对象的引用
    b. 重写 new 方法
    c. 如果 类属性 is None,调用父类方法分配空间,并在类属性中记录结果
    d. 返回 类属性 中记录的 对象引用
    在这里插入图片描述
class MusicPlayer(object):

    # 定义类属性记录单例对象引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否已经被赋值
        if cls.instance is None:
            cls.instance = super().__new__(cls)

        # 2. 返回类属性的单例引用
        return cls.instance
  • 只执行一次初始化工作
    • 在每次使用 类名() 创建对象时,Python 的解释器都会自动调用两个方法:
      • __new__ 分配空间
      • __init__ 对象初始化
    • 在上一小节对 __new__ 方法改造之后,每次都会得到 第一次被创建对象的引用
    • 但是:初始化方法还会被再次调用
  • 需求
    • 让 初始化动作 只被 执行一次
  • 解决办法
  1. 定义一个类属性 init_flag 标记是否 执行过初始化动作,初始值为 False
  2. init 方法中,判断 init_flag,如果为 False 就执行初始化动作
  3. 然后将 init_flag 设置为 True
  4. 这样,再次 自动 调用 init 方法时,初始化动作就不会被再次执行 了
class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None
    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否是空对象
        if cls.instance is None:
            # 2. 调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3. 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):

        if not MusicPlayer.init_flag:
            print("初始化音乐播放器")

            MusicPlayer.init_flag = True


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

八、多态

1. 面向对象三大特性

  1. 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
    ○ 定义类的准则
  2. 继承 实现代码的重用,相同的代码不需要重复的编写
    ○ 设计类的技巧
    ○ 子类针对自己特有的需求,编写特定的代码
  3. 多态 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果
    ○ 多态 可以 增加代码的灵活度
    ○ 以 继承 和 重写父类方法 为前提
    ○ 是调用方法的技巧,不会影响到类的内部设计
    在这里插入图片描述

2. 多态案例演练

  • 需求
  1. 在 Dog 类中封装方法 game
    ○ 普通狗只是简单的玩耍
  2. 定义 XiaoTianDog 继承自 Dog,并且重写 game 方法
    ○ 哮天犬需要在天上玩耍
  3. 定义 Person 类,并且封装一个 和狗玩 的方法
    ○ 在方法内部,直接让 狗对象 调用 game 方法

在这里插入图片描述

  • 案例小结
    • Person 类中只需要让 狗对象 调用 game 方法,而不关心具体是 什么狗
      • game 方法是在 Dog 父类中定义的
    • 在程序执行时,传入不同的 狗对象 实参,就会产生不同的执行效果
  • 多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!
class Dog(object):

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

    def game(self):
        print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(Dog):

    def game(self):
        print("%s 飞到天上去玩耍..." % self.name)


class Person(object):

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

    def game_with_dog(self, dog):

        print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))

        # 让狗玩耍
        dog.game()


# 1. 创建一个狗对象
# wangcai = Dog("旺财")
wangcai = XiaoTianDog("飞天旺财")

# 2. 创建一个小明对象
xiaoming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)

九、继承

1. 单继承

1.1 继承的概念、语法和特点
  • 继承的概念:子类 拥有 父类 的所有 方法 和 属性
    在这里插入图片描述

  • 继承的语法

    class 类名(父类名):
    
        pass
    
    • 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
    • 子类 中应该根据 职责,封装 子类特有的 属性和方法
  • 专业术语

    • Dog 类是 Animal 类的子类,Animal 类是 Dog 类的父类,Dog 类从 Animal 类继承
    • Dog 类是 Animal 类的派生类,Animal 类是 Dog 类的基类,Dog 类从 Animal 类派生
  • 继承的传递性

    • C 类从 B 类继承,B 类又从 A 类继承
    • 那么 C 类就具有 B 类和 A 类的所有属性和方法
  • 子类 拥有 父类 以及 父类的父类 中封装的所有 属性 和 方法

  • 提问:哮天犬 能够调用 Cat 类中定义的 catch 方法吗?

  • 答案:不能,因为 哮天犬 和 Cat 之间没有 继承 关系

1.2 方法的重写
  • 子类 拥有 父类 的所有 方法 和 属性

  • 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发

  • 应用场景

    • 当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
      在这里插入图片描述
  • 重写 父类方法有两种情况:

  1. 覆盖 父类的方法
  2. 对父类方法进行 扩展
  • 覆盖父类的方法

    • 如果在开发中,父类的方法实现 和 子类的方法实现,完全不同

    • 就可以使用 覆盖 的方式,在子类中 重新编写 父类的方法实现

    • 具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法并且实现

    • 重写之后,在运行时,只会调用 子类中重写的方法,而不再会调用 父类封装的方法

  • 对父类方法进行 扩展

    • 如果在开发中,子类的方法实现 中 包含 父类的方法实现
    • 父类原本封装的方法实现 是 子类方法的一部分
  • 就可以使用 扩展 的方式
    a. 在子类中 重写 父类的方法
    b. 在需要的位置使用 super().父类方法 来调用父类方法的执行
    c. 代码其他的位置针对子类的需求,编写 子类特有的代码实现

  • 关于 super

    • 在 Python 中 super 是一个 特殊的类
    • super() 就是使用 super 类创建出来的对象
    • 最常 使用的场景就是在 重写父类方法时,调用 在父类中封装的方法实现
  • 调用父类方法的另外一种方式(知道)

    • 在 Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
  • 这种方式,目前在 Python 3.x 还支持这种方式

  • 这种方法 不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改

  • 提示

    • 在开发时,父类名 和 super() 两种方式不要混用
    • 如果使用 当前子类名 调用方法,会形成递归调用,出现死循环
1.3 父类的 私有属性 和 私有方法
  • 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法

  • 子类对象 可以通过 父类 的 公有方法 间接 访问到 私有属性 或 私有方法

  • 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问

  • 私有属性、方法 通常用于做一些内部的事情
    在这里插入图片描述

  • B 的对象不能直接访问 __num2 属性

  • B 的对象不能在 demo 方法内访问 __num2 属性

  • B 的对象可以在 demo 方法内,调用父类的 test 方法

  • 父类的 test 方法内部,能够访问 __num2 属性和 __test 方法

2. 多继承

  • 概念
    • 子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法
    • 例如:孩子 会继承自己 父亲 和 母亲 的 特性
      在这里插入图片描述
class 子类名(父类名1, 父类名2...)
    pass
2.1 多继承的使用注意事项
  • 问题的提出
    • 如果 不同的父类 中存在 同名的方法,子类对象 在调用方法时,会调用 哪一个父类中的方法呢?
  • 提示:开发时,应该尽量避免这种容易产生混淆的情况! —— 如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承
    在这里插入图片描述
  • Python 中的 MRO —— 方法搜索顺序(知道)
  • Python 中针对 类 提供了一个 内置属性 __mro__ 可以查看 方法 搜索顺序
  • MRO 是 method resolution order,主要用于 在多继承时判断 方法、属性 的调用 路径
print(C.__mro__)

# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
  • 在搜索方法时,是按照 mro 的输出结果 从左至右 的顺序查找的
  • 如果在当前类中 找到方法,就直接执行,不再搜索
  • 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到方法,程序报错
2.2 新式类与旧式(经典)类
  • object 是 Python 为所有对象提供的 基类,提供有一些内置的属性和方法,可以使用 dir 函数查看

    • 新式类:以 object 为基类的类,推荐使用
    • 经典类:不以 object 为基类的类,不推荐使用
    • 在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object 作为该类的 基类 —— Python 3.x 中定义的类都是 新式类
    • 在 Python 2.x 中定义类时,如果没有指定父类,则不会以 object 作为 基类
  • 新式类 和 经典类 在多继承时 —— 会影响到方法的搜索顺序

  • 为了保证编写的代码能够同时在 Python 2.x 和 Python 3.x 运行!

  • 今后在定义类时,如果没有父类,建议统一继承自 object

class 类名(object):
    pass
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬小帽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值