第9章 面向对象三大基本特征

第9章 面向对象三大基本特征

9.1 封装

9.1.1 封装的目的/意义

不管是 Java 还是 Python,封装都是面向对象的三大特性之一,核心目的完全相同,只有实现方式不同:

  1. 隐藏内部实现:把类的私有数据(属性)和操作数据的方法绑定在一起,外部无需知道类的内部如何存储、如何处理数据;例如:list列表
  2. 保证数据安全:限制外部对属性的随意修改,通过统一接口做数据校验、日志记录、联动更新等,避免非法数据进入;
  3. 降低耦合度:外部仅通过类暴露的接口和类交互,后续类的内部实现修改时,只要接口不变,外部代码无需任何调整。

Java 把「私有化属性」作为实现封装的唯一常规手段,所以慢慢形成了「私有化 = 封装」的认知;而 Python 提供了多种更灵活的方式,私有化只是其中一种(还不是最常用的),这是两者的核心差异。

9.1.2 约定式封装:单下划线 _attr

这是 Python 中实现封装的标准方式,没有语法强制限制,而是通过行业编程约定定义「类的内部成员」:

  • 写法:属性 / 方法以单个下划线开头,如self._agedef _calc_score(self):
  • 含义:向其他开发者传递明确信号 ——「这是类的内部实现细节,外部代码不要直接访问 / 修改 / 调用」;
  • 约束:代码检查工具(PyCharm等)会对外部访问_attr的行为给出警告,团队开发中靠这个规范代码,而非 Python 语法限制;
  • 核心作用:实现「逻辑上的封装」,区分「外部可调用的接口」和「内部自用的实现」。
# 定义类
class Student:
    def __init__(self, math, english):
        self.math = math
        self.english = english
        self._total = math + english  # 内部属性,外部无需关注

    # 内部实现:单下划线标识,外部不要调用
    def _calc_average(self):
        return self._total / 2

    # 外部接口:公开方法,供外部调用
    def get_average(self):
        return self._calc_average()  # 调用内部方法

# 测试
# 创建对象
s = Student(90, 80)
print(s.get_average())  # 外部调用公开接口,正确用法
print(s._total) # 外部访问内部属性,,PyCharm会给出警告(语法上能运行)
print(s._calc_average())  # 外部调用内部方法,PyCharm会给出警告(语法上能运行)

9.1.3 接口式封装:@property装饰器

这是 Python 中最优雅的封装方式,也是替代 Java「getter/setter」的核心方案,核心目标是 「给属性加逻辑,同时保持外部访问接口的简洁性」

  • 把 getter/setter 方法的逻辑绑定到属性名上,外部依然用对象.属性访问 / 修改,无需像 Java 那样写getXxx()/setXxx()

    • 巧妙使用@property装饰器标记一个返回私有属性值的方法,这类方法通常称为getter方法
    • 巧妙使用@getter方法名.setter装饰器标记一个设置私有属性值的方法,这类方法通常称为setter方法

  • 封装逻辑对外部完全透明,后续如果给原本的公开属性加校验,外部代码无需任何修改



  • 注意:@property装饰的方法不要和变量重名,否则可能导致无限递归。


原来Person类的代码:

# 定义类
class Person:
    def __init__(self, name=None, age=None):
        self.name = name
        self.age = age

新修改Person类的代码

# 新定义Person类
class Person:
    def __init__(self, name=None, age=None):
        self.name = name
        self.__age = age

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age<0 or age>100:
            raise ValueError("年龄超出范围")
        self.__age = age

测试代码(不变)

# 测试
# 创建对象
p = Person("张三",18)
print(f"姓名:{p.name},年龄:{p.age}")
p.name = "张三丰"
p.age = 23
print(f"姓名:{p.name},年龄:{p.age}")

9.1.4 名称改写式封装:双下划线 __attr

Python 中没有像 Java 那样的private关键字,双下划线__attr是唯一的「语法级别的封装手段」,这是最接近其他编程语言的私有化方式了。但本质是名称改写(name mangling),而非真正的私有。

  • 定义双下划线属性__secret时,Python 会在编译时将其改写,外部访问是_类名__secret。外部直接通过对象.__secret实例属性类名.__secret类属性访问会报错,只能通过改写后的名称强行访问,即对象._类名__secret实例属性类名._类名__secret类属性
  • __方法类似。

示例一

# 定义类
class Person:
    """人的类"""
    __home = "earth"  # 私有类属性

    def __init__(self, name,age=0):
        self.__name = name # 私有实例属性
        if self.__check_age(age):
            self.__age = age # 私有实例属性

    def __check_age(self,age): # 私有方法
        if age<0 or age>100:
            raise ValueError("年龄超出范围")
        return True

    def __str__(self):
        return f"姓名:{self.__name},年龄:{self.__age}"

# 演示使用
p = Person("张三", 18) # 创建对象
# p2 = Person("张三", -18) # 创建对象
print(p)

# print(Person.__home) # 报错, __home 属性是私有的,不能直接访问
# print(p.__name)  # 报错, __name 属性是私有的,不能直接访问
# p.__check_age(100) # 报错,__check_age() 是私有方法,不能直接访问

print(f"Person.__home = {Person._Person__home}") # __home 名称改写为 _Person__home
print(f"p.__name = {p._Person__name}")  # __name名称改写为 _Person__name
print(f"p.__age = {p._Person__age}") # __age名称改写为 _Person__age
print(p._Person__check_age(20)) # __check_age私有方法 名称改写为  _Person__check_age

示例二

这种方式在 Python 中极少用于日常封装,原因有二:

  1. 依然可以通过改写后的名称强行访问,不是「真正的私有」;
  2. 写法繁琐,破坏了 Python 的简洁性,且违背「约定优于强制」的设计哲学;
  3. 仅在类的继承场景中,需要防止子类意外覆盖父类内部属性时,才会偶尔使用。
# 定义父类
class Parent:
    def __init__(self):
        self.__info = "父类的内部信息"  # 改写为 _Parent__info

# 定义子类
class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__info = "子类的信息"  # 改写为 _Child__info,不会覆盖父类的__info

# 测试
# 创建对象
p = Parent()
c = Child()

# print(p.__info)  # 直接访问,报错:AttributeError
print(p._Parent__info)  # 强行访问,输出:父类的内部信息
print(c._Child__info)   # 输出:子类的信息
print(c._Parent__info)  # 子类能拿到父类改写后的属性,输出:父类的内部信息

选择:_属性__属性

✅ 用单下划线 _name(约定私有)的场景

  • 类的内部成员,希望子类能继承 / 修改,但不希望外部业务代码随意访问;
  • 模块内的变量 / 函数,仅希望在模块内部使用,不希望被import *导入;
  • 临时的、辅助性的成员,开发调试时需要便捷访问,无需强保护。

✅ 用双下划线 __name(原生私有)的场景

  • 类的核心成员,绝对不希望子类重写 / 修改,避免命名冲突;
  • 类的敏感成员(如密码、核心配置),需要语言层面的保护,防止外部代码误访问 / 修改;
  • 框架 / 库的底层类设计,需要严格的封装,避免使用者随意修改内部逻辑。

9.1.5 最小封装原则

Python 的「伪私有」本质:Python没有真正的、不可突破的私有,哪怕是双下划线的__name,也能通过_类名__name绕开访问,这和 Java/C++ 的private(真正的语法私有,外部完全无法访问)有本质区别。

Python 的设计哲学里有一句经典的话:“We are all consenting adults here”(我们都是成年人),意思是 Python 不会强制限制开发者的任何操作,更倾向于 “约定优于强制”

Python 的私有设计思路是:阻止粗心的错误,而非阻止恶意的破坏。无论是单下划线的 “约定私有”,还是双下划线的 “原生私有”,都是为了规范协作,而非限制开发者的操作。

Python 社区对封装的理解,总结成一句话就是:「能不封装就不封装,需要封装时极简封装」,这和 Java 的「所有属性都要封装」形成鲜明对比。

具体落地规则:

  1. 简单属性(无校验、无逻辑、无内部关联):直接公开,不用_、不用property,比如self.nameself.gender
  2. 类内自用的属性 / 方法(外部无需关注):用单下划线_attr做约定式封装,区分内部实现和外部接口;
  3. 需要数据校验 / 日志 / 联动逻辑的属性:用property做接口式封装,保持外部访问的简洁性;
  4. 继承场景中防止子类覆盖:才考虑用双下划线__attr做名称改写式封装。

这种原则的核心是:避免过度封装—— 封装的目的是为了让代码更安全、更易维护,而不是为了封装而封装,Python 拒绝为了「形式上的封装」写繁琐的代码。

9.2 继承

在 Python 中,所有没有显式指定父类的类,默认继承自objectobject是 Python 中所有类的根类(顶级父类)

9.2.1 继承的目的和意义

让一个子类(派生类)复用另一个父类(基类 / 超类)的属性和方法,同时子类可以根据需求扩展、重写父类的内容,实现代码复用功能扩展,避免重复编写相同代码。

继承的核心是 “is a” 关系 (子类是父类的一种),当两个类满足 “子类是父类的具体实现 / 特殊类型” 时,适合用继承:

  1. 是一种动物 → Dog 继承 Animal;
  2. 学生是一种人 → Student 继承 Person;
  3. 矩形是一种图形 → Rectangle 继承 Shape。

简单理解:子类是父类的 “升级版”,天生拥有父类的所有能力,还能自己加新能力、改造原有能力。

继承的优点:

  1. 代码复用:父类的通用逻辑只写一次,所有子类直接复用,减少冗余;
  2. 功能扩展:子类可通过新增方法 / 重写方法快速扩展功能,符合 “开闭原则”(对扩展开放,对修改关闭);
  3. 结构清晰:通过继承梳理类之间的层级关系,代码结构更直观。

继承的缺点:

  1. 耦合度高:子类和父类强关联,父类的修改会直接影响所有子类,增加维护成本;
  2. 多继承复杂:多继承会导致 MRO、菱形继承等问题,代码可读性和可维护性下降;
  3. 灵活性低:继承是静态的,编译时确定,无法动态改变继承关系。

9.2.2 演示单继承

语法格式:

class 子类名(父类名):
    子类体

构造方法__init__是类的特殊方法,用于初始化实例属性

  1. 如果子类没有重写__init__,实例化子类时会自动调用父类的__init__
  2. 如果子类重写了__init__,实例化子类时会优先执行子类的__init__,父类的__init__不会自动执行,需要通过super()等方式手动调用(否则父类的初始化逻辑会丢失)。

示例演示:

"""
    演示单继承
"""
# 定义父类
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(f"{self.name}吃东西....")

# 定义子类
class Student(Person):
    def __init__(self, name,age,score): # 子类构造方法
        super().__init__(name,age) # 调用父类的构造方法,为继承的name和age初始化代码
        self.score = score

    def study(self):
        print(f"{self.name}在学习,年龄为{self.age},得分为{self.score}")

# 定义子类
class Teacher(Person):
    def __init__(self, name, age, major): # 子类构造方法
        super().__init__(name, age)
        self.major = major

    def teach(self):
        print(f"{self.name}老师,年龄为{self.age},主攻{self.major}")

# 定义子类
class Doctor(Person):
    def work(self):
        print(f"{self.name}医生,年龄为{self.age}")

# 测试
# 创建对象
s = Student("张三", 18, 90)
s.eat()
s.study()

t = Teacher("李四", 28, "Python")
t.eat()
t.teach()

d = Doctor("王五", 38)
d.eat()
d.work()

9.2.3 属性覆盖问题

父类的同名类属性不会被覆盖,但是父类非__attr私有的同名实例属性会被覆盖。

  • 如果父子类同名属性逻辑意义相同,那么父类声明过了,子类不必重新定义,直接使用即可。
  • 如果父子类同名属性逻辑意义不同,那么应该使用不同的名字进行区别。
  • 如果父类某个属性绝对不希望子类覆盖 / 修改,那么就应该使用__attr方式私有化处理。
class Parent:
    # 类属性
    a = 10
    _b = 10
    __c = 10
    def __init__(self):
        # 实例属性
        self.x = 10
        self._y = 10
        self.__z = 10

class Child(Parent):
    a = 20 # 父类类属性a不会覆盖(语法允许,但是不建议)
    _b = 20 # 父类类属性_b不会覆盖(语法允许,但是不建议)
    __c = 20 # 父类类属性__c不会覆盖(语法允许,但是不建议)
    def __init__(self):
        super().__init__()
        # self.x = 20 # 父类实例属性x被覆盖(语法允许,但是不建议)
        # self._y = 20 # 父类实例属性_y被覆盖(语法允许,但是不建议)
        self.__z = 20 # 父类实例属性__z不会被覆盖,因为父类属性__z被改写为_Parent__z(语法允许,但是不建议)

    def show(self):
        print("父子类的类属性:")
        print(f"父类的a={Parent.a},子类a={Child.a}")
        print(f"父类的_b={Parent.a},子类_b={Child._b}")
        print(f"父类的__c={Parent._Parent__c},子类__c={Child.__c}")
        print("父子类的实例属性:")
        print(f"x={self.x}") # 如果子类没有同名的x属性,则会直接访问父类的x属性
        print(f"_y={self._y}") # 如果子类没有同名的_y属性,则会直接访问父类的_y属性
        print(f"__z={self.__z}") # 如果子类没有同名的__z属性,则会报错,因为父类属性__z被改写为_Parent__z
        print(f"_Parent__z={self._Parent__z}")

# 测试
c = Child()
c.show()

9.2.4 重写父类的方法

子类可以重写父类的方法,方法名相同即为重写。python中没有方法重载的概念,后定义的同名方法就会覆盖之前定义的方法。

1、重写父类实例方法

可以在子类中使用super().实例方法名()来调用父类非__method私有的实例方法:

"""
    子类复用或重写父类的方法
"""
# 定义父类
class Flyable:
    """
    飞行能力类
    注意:使用此类的子类需要提供 name 属性
    """
    def fly(self):
        print(f"{self.name} 正在飞行")

# 定义父类
class Bird:
    """鸟类基类"""
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print(f"{self.name} 在鸣叫")

    def eat(self):
        print(f"{self.name}吃东西....")

# 定义子类
class Duck(Bird, Flyable):
    """鸭子类 - 继承鸟类和飞行能力"""
    def __init__(self, name, price):
        super().__init__(name)
        self.price = price

    def catch_fish(self): # 添加新方法
        print(f"{self.name} 抓 fish")

    def make_sound(self): # 重写父类Bird的make_sound方法
        print(f"{self.name} 嘎嘎嘎")

    def eat(self, food): # 重写父类Bird的eat方法,允许形参列表不同
        print(f"{self.name} 吃{food}....")

    def fly(self): # 重写父类Flyable的fly方法
        super().fly() # 调用父类的方法
        # Flyable.fly(self) # 调用父类的方法
        print(f"{self.name} 飞也飞不高")

# 测试
duck = Duck("小黄鸭", 10)
duck.catch_fish()
duck.make_sound()
duck.eat("虫子")
duck.fly()

2、重写父类的类方法和静态方法(了解)

可以在子类中用如下方式调用父类非__method私有的类方法和静态方法。

  • 父类名.类方法名() :此时父类方法cls参数接收的是父类类型(推荐)
  • 父类名.静态方法():没有cls参数。(推荐)
  • super().类方法名() :此时父类方法cls参数接收的是子类类型(可以,但不推荐)
  • super().静态方法名():子类中不支持该方式访问父类的静态方法。(不支持)
# 定义父类
class Bird:
    info = "鸟类"

    @classmethod
    def print_info(cls):
        print(f"Bird类.print_info,cls类名是{cls.__name__},{cls.info=},{Bird.info=}")

    @staticmethod
    def show():
        print(f"Bird类.show,{Bird.info=}")

# 定义子类
class Duck(Bird):
    info = "鸭子"

    @classmethod
    def print_info(cls):
        super().print_info() # 给父类print_info方法cls的参数是Duck类
        # Bird.print_info() # 给父类print_info方法cls的参数是Bird类
        print(f"Duck类.print_info,cls类名是{cls.__name__},{cls.info=},{Duck.info=}")

    @staticmethod
    def show():
        Bird.show()  # 给父类show方法cls的参数是Bird类
        print(f"Duck类.show,{Duck.info=}")

# 测试
Duck.print_info()
Duck.show()

9.2.5 演示多继承

  • 调用方法时先在子类中查找,若不存在则从左到右依次查找父类中是否包含方法。

语法格式:

class 类名(父类1, 父类2, ...):
    类体

示例演示:

"""
    演示多继承
    功能组合:通过多继承将不同的能力(飞行、游泳、行走)组合到一个类中
    清晰结构:每个能力类职责单一,便于维护和扩展
"""
# 定义父类
class Flyable:
    """
    飞行能力类
    注意:使用此类的子类需要提供 name 属性
    """
    def fly(self):
        print(f"{self.name} 正在飞行")

# 定义父类
class Swimming:
    """
    游泳能力类
    注意:使用此类的子类需要提供 name 属性
    """
    def swim(self):
        print(f"{self.name} 正在游泳")

# 定义父类
class Bird:
    """鸟类基类"""
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print(f"{self.name} 在鸣叫")

# 定义子类
class Duck(Bird, Flyable, Swimming):
    """鸭子类 - 继承鸟类和多种能力"""
    def __init__(self, name):
        super().__init__(name)

    def catch_fish(self):
        print(f"{self.name} 抓 fish")

# 使用示例
duck = Duck("小黄鸭")
duck.fly()
duck.swim()
duck.make_sound()
duck.catch_fish()

混入类(Mixin Class)是一种设计模式,用于将特定功能模块化,然后通过多重继承组合到主类中。

例如:

  • Flyable、Swimming都是混入类,每个类都提供单一功能:飞行、游泳。这些类都假设子类具有 name 属性。
  • Duck 类通过多继承组合了多种能力。Duck类先继承了 Bird类 的基础属性和方法,然后通过混入类获得了飞行、游泳能力。

混入类模式的特点:

  • 混入类通常不单独实例化。它们一般不包含 init 方法或只包含简单的初始化。通过约定或文档说明混入类的依赖(如要求子类提供 name 属性)。
  • 主要目的是提供可重用的功能。
  • 通过多重继承将功能组合到目标类中。
  • 降低了代码重复,提高了代码的模块化程度。

混入类的设计优势:

  • 单一职责:每个混入类只负责一种能力
  • 可重用性:混入类可以在不同类中重复使用
  • 灵活性:可以根据需要组合不同的能力
  • 清晰结构:功能模块化,便于维护和扩展

9.2.6 方法解析顺序MRO

1、什么是MRO

如果多个父类有同名方法,子类调用时会按固定顺序查找,这个顺序就是方法解析顺序(Method Resolution Order,MRO)。MRO(Method Resolution Order)是 Python 为多继承设计的方法 / 属性查找规则,本质是一个类的线性搜索顺序列表。可使用 类名.mro 访问类的继承链来查看方法解析顺序。

Python3 中 MRO 遵循C3 算法,简单总结:

  1. 先找子类自己的方法;
  2. 再按父类定义的顺序从左到右查找;
  3. 最后找共同的祖先类

C3 算法的三个关键性质:

  1. 局部优先顺序:子类优先于父类,同一父类的多个子类保持声明顺序。例如图1是先B在C,图2是先C后B。
  2. 一致性:如果类D继承类B,类B继承类A ,那么在MRO中D应该在B之前,B应该在A之前。即E(D,B,A)是对的,而E(B,D)、E(A,B)是错的。
  3. 单调性:子类的MRO不会破坏父类的 MRO 顺序(避免查找混乱)。即如果在所有父类的MRO中,类C、B都在类A之前,那么在子类的MRO中,B、C也应该在A之前。而在 Python 2.2 之前使用的是 DFS(深度优先搜索)算法,此时图1的MRO顺序就为DBAC,这就破坏了子类C优先于父类A的原则,即破坏了单调性(单次调用)。自 Python 2.3 起引入C3算法,此时图1的MRO顺序就为DBCA,遵循了子类B、C优先于A类的原则。
image-20260205203601948

2、演示MRO

情况一:按照MRO顺序,找到了对应的方法就停止。

情况二:按照MRO顺序,找到了对应的方法,但对应方法中还有super().方法()语句,那么继续沿着MRO方向调用父类的方法,直到没有super().方法()语句为止。

# 定义父类
class A:
    def show_one(self):
        print("A的show_one")
    def show_two(self):
        print("A的show_two")
    def show_three(self):
        print("A的show_three")
    def show_four(self):
        print("A的show_four")

# 定义父类
class B(A):
    def show_one(self):
        print("B")
    def show_two(self):
        print("B的show_two")
    def show_three(self):
        print("B的show_three")
        super().show_three()
    def show_four(self):
        print("B的show_four")
        super().show_four()

# 定义父类
class C(A):
    def show_one(self):
        print("C的show_one")
    def show_two(self):
        print("C的show_two")
    def show_three(self):
        print("C的show_three")
    def show_four(self):
        print("C的show_four")
        super().show_four()

# 定义子类
class D(B,C):
    def show_one(self):
        print("D的show_one")
    def show_two(self):
        print("D的show_two")
        super().show_two()
    def show_three(self):
        print("D的show_three")
        super().show_three()
    def show_four(self):
        print("D的show_four")
        super().show_four()

# 测试
d = D()
print("查看D类的mro:")
print(D.mro())
print()
print("调用show_one方法".center(50, "-"))
d.show_one()
print("调用show_two方法".center(50, "-"))
d.show_two()
print("调用show_three方法".center(50, "-"))
d.show_three()
print("调用show_four方法".center(50, "-"))
d.show_four()

3、MRO解决了菱形继承问题

经典结构:D继承BCBC又都继承自A,形成一个菱形(钻石)结构:

image-20260205220318491

如果没有 MRO 的 C3 算法,调用D的方法时,A的方法可能会被B 和 C 各调用一次,造成逻辑重复、资源浪费。C3 算法会将共同基类(A)放在 MRO 列表的最后一个父类之后,保证共同基类只被查找一次,彻底避免重复执行的问题。

注意:在多继承中,super () 的查找对象不是当前类的父类,而是 MRO 列表的下一个父类,这是 super () 的核心底层逻辑,也是 MRO 解决菱形继承的关键。

4、避坑指南

  • 原则 1:多继承时,先查 MRO 再写代码
    • 只要写了多继承代码,第一时间用类名.mro()查看查找顺序,确认方法 / 属性的执行逻辑,避免凭直觉写代码导致的歧义(比如以为会执行 C 的方法,实际 MRO 先找了 B)。
  • 原则 2:避免多继承中大量的同名方法
    • MRO 能解决同名方法的冲突,但大量的同名方法会让代码的可读性急剧下降(别人看代码时必须查 MRO 才能知道执行逻辑)。
    • 实际开发中,多继承尽量用于复用不同父类的不同方法(父类之间无同名成员),而非解决同名方法的冲突。

9.2.7 多继承时指定调用某父类方法

方式一:父类名.方法名 (实例,参数)

示例1

class A:
    def func(self):
        print("A的func")

class B(A):
    def func(self):
        print("B的func")

class C(A):
    def func(self):
        print("C的func")

class D(B, C):
    pass

d = D()
# 1. 按MRO顺序,默认调用B的func
d.func()  # 输出:B的func

# 2. 强行调用C的func(语法:父类名.方法名(实例))
C.func(d) # 输出:C的func

# 3. 强行调用A的func
A.func(d) # 输出:A的func

示例2

class A:
    def func(self):
        print("A的func")

class B(A):
    def func(self):
        print("B的func")

class C(A):
    def func(self):
        print("C的func")

class D(B, C):
    def func(self):
        B.func(self)#指定调用B的func
        # C.func(self)#指定调用C的func
        # A.func(self)#指定调用A的func

d = D()
d.func()

方式二:super().方法名(参数)或super(类型,self).方法名(参数)

class A:
    def func(self):
        print("A的func")

class B(A):
    def func(self):
        print("B的func")

class C(A):
    def func(self):
        print("C的func")

class D(B, C):
    def func(self):
        # super().func() #根据mro,调用B的func
        super(D, self).func()#根据mro顺序,这里找到的是D的下一个,即B的func
        # super(B, self).func()#根据mro顺序,这里找到的是B的下一个,即C的func
        # super(C, self).func()#根据mro顺序,这里找到的是C的下一个,即A的func

d = D()
d.func()

9.2.8 多继承时初始化问题

方式一:父类名.__init__ (实例,参数)

#总的父类
class Goods:
    def __init__(self, goods_name,goods_price):
        self.goods_name = goods_name
        self.goods_price = goods_price

# 父类1:交通工具(负责车辆行驶相关属性与方法)
class Vehicle(Goods):
    def __init__(self, goods_name,goods_price, max_speed, seat_num):
        # 交通工具独有属性
        self.max_speed = max_speed  # 最高时速
        self.seat_num = seat_num  # 座位数
        # super().__init__(goods_name,goods_price)#错误,
        # 因为对于子类ElectricCar来说,super().__init__会找Electronic的__init__,而不是Goods的__init__
        Goods.__init__(self, goods_name,goods_price)

    def drive(self):
        """行驶方法"""
        print(f"最大速度{self.max_speed}km/h,{self.seat_num}座车辆正在行驶")

# 父类2:电子产品(负责电池、充电相关属性与方法)
class Electronic(Goods):
    def __init__(self, goods_name,goods_price, battery_cap):
        # 电子产品独有属性
        self.battery_cap = battery_cap  # 电池容量 kWh
        self.electric = 100  # 当前电量百分比
        # super().__init__(goods_name,goods_price)#有风险
        Goods.__init__(self, goods_name, goods_price)

    def charge(self):
        """充电方法"""
        self.electric = 100
        print(f"{self.battery_cap}kWh电池已充满,电量100%")

# 子类:电动车 ElectricCar,同时继承两个父类
# 同时拥有交通工具+电子产品两套独立属性
class ElectricCar(Vehicle, Electronic):
    def __init__(self, goods_name,goods_price, max_speed, seat_num, battery_cap, autopilot_level):
        # 分别调用两个父类构造方法,继承各自属性
        Vehicle.__init__(self, goods_name,goods_price, max_speed, seat_num)
        Electronic.__init__(self, goods_name,goods_price, battery_cap)
        # 子类自己独有的属性
        self.autopilot_level = autopilot_level

    def show_info(self):
        # 同时打印两个父类继承来的属性 + 自身属性
        print("=====电动车完整信息=====")
        print(f"自动驾驶等级:{self.autopilot_level}")
        # 来自Goods商品父类
        print(f"商品名称:{self.goods_name}")
        print(f"商品价格:{self.goods_price}万")
        # 来自Vehicle交通工具父类
        print(f"最高时速:{self.max_speed} km/h")
        print(f"座位数量:{self.seat_num} 座")
        # 来自Electronic电子产品父类
        print(f"电池容量:{self.battery_cap} kWh")
        print(f"当前电量:{self.electric} %")

# 测试代码
if __name__ == "__main__":
    # 创建子类实例
    tesla = ElectricCar(goods_name="特斯拉",goods_price=50,max_speed=220, seat_num=5, battery_cap=75, autopilot_level="L4")

    # 查看所有继承+自有属性
    tesla.show_info()
    print("n--调用交通工具方法--")
    tesla.drive()
    print("n--调用电子产品充电方法--")
    tesla.charge()

方式二:super().方法名(参数, **params)

class Goods:
    def __init__(self, goods_name,goods_price,**params):
        self.goods_name = goods_name
        self.goods_price = goods_price
        super().__init__(**params)

# 父类1:交通工具(负责车辆行驶相关属性与方法)
class Vehicle(Goods):
    def __init__(self, goods_name,goods_price, max_speed, seat_num,**params):
        # 交通工具独有属性
        self.max_speed = max_speed  # 最高时速
        self.seat_num = seat_num  # 座位数
        super().__init__(goods_name=goods_name,goods_price=goods_price,**params)

    def drive(self):
        """行驶方法"""
        print(f"最大速度{self.max_speed}km/h,{self.seat_num}座车辆正在行驶")

# 父类2:电子产品(负责电池、充电相关属性与方法)
class Electronic(Goods):
    def __init__(self, goods_name,goods_price, battery_cap,**params):
        # 电子产品独有属性
        self.battery_cap = battery_cap  # 电池容量 kWh
        self.electric = 100  # 当前电量百分比
        super().__init__(goods_name=goods_name,goods_price=goods_price,**params)

    def charge(self):
        """充电方法"""
        self.electric = 100
        print(f"{self.battery_cap}kWh电池已充满,电量100%")

# 子类:电动车 ElectricCar,同时继承两个父类
# 同时拥有交通工具+电子产品两套独立属性
class ElectricCar(Vehicle, Electronic):
    def __init__(self, goods_name,goods_price, max_speed, seat_num, battery_cap, autopilot_level):
        # 调用父类构造方法,继承各自属性
        super().__init__(goods_name=goods_name,goods_price=goods_price,max_speed=max_speed,seat_num=seat_num,battery_cap=battery_cap)
        # 子类自己独有的属性
        self.autopilot_level = autopilot_level

    def show_info(self):
        # 同时打印两个父类继承来的属性 + 自身属性
        print("=====电动车完整信息=====")
        print(f"自动驾驶等级:{self.autopilot_level}")
        # 来自Goods商品父类
        print(f"商品名称:{self.goods_name}")
        print(f"商品价格:{self.goods_price}万")
        # 来自Vehicle交通工具父类
        print(f"最高时速:{self.max_speed} km/h")
        print(f"座位数量:{self.seat_num} 座")
        # 来自Electronic电子产品父类
        print(f"电池容量:{self.battery_cap} kWh")
        print(f"当前电量:{self.electric} %")

# 测试代码
if __name__ == "__main__":
    # 创建子类实例
    tesla = ElectricCar(goods_name="特斯拉",goods_price=50,max_speed=220, seat_num=5, battery_cap=75, autopilot_level="L4")

    # 查看所有继承+自有属性
    tesla.show_info()
    print("n--调用交通工具方法--")
    tesla.drive()
    print("n--调用电子产品充电方法--")
    tesla.charge()

9.3 多态

多态(Polymorphism)是一种编程思想 / 特性。多态的字面意思是 “多种形态”,在编程中的核心定义是:同一操作作用于不同的对象上,可以产生不同的执行结果。简单说就是:不同的对象,对同一个方法调用,能做出不同的响应。

实现多态效果的方式有2种:

  • 基于继承+方法重写的多态:这是大多数面向对象编程语言的实现方式(例如Java)
  • 基于鸭子类型的多态:这是 Python 实现多态的核心方式

9.3.1 基于继承的多态

"""
    演示多态
"""
# 定义父类
class Animal:
    def go(self):
        pass

# 定义子类
class Dog(Animal):
    def go(self):
        print("小狗跑")

# 定义子类
class Fish(Animal):
    def go(self):
        print("鱼儿游")

# 定义子类
class Bird(Animal):
    def go(self):
        print("鸟儿飞")

# 定义函数
def go(animal):
    animal.go()  # 将不同的实例传入,执行不同的方法

# 创建对象,调用函数
dog = Dog()
fish = Fish()
bird = Bird()
go(dog)
go(fish)
go(bird)

9.3.2 基于鸭子类型的多态

Python(动态类型语言):不需要继承、不需要声明类型,完全靠鸭子类型实现多态 —— 只要不同对象有同名的方法,调用这个方法时就会表现出不同行为,就可以实现多态效果。

# 定义类
class Dog:
    def go(self):
        print("小狗跑")

# 定义类
class Fish:
    def go(self):
        print("鱼儿游")

# 定义类
class Bird:
    def go(self):
        print("鸟儿飞")

# 定义函数
def go(animal):
    animal.go()  # 将不同的实例传入,执行不同的方法

# 创建对象,调用函数
dog = Dog()
fish = Fish()
bird = Bird()
go(dog)
go(fish)
go(bird)

什么是鸭子类型?

鸭子类型(Duck Typing)是动态类型语言中一个核心的编程风格,其精髓可以用一句话概括:

如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。

翻译成编程术语就是:一个对象的“有效性”不取决于它继承自哪个类,而取决于它有没有实现特定的方法或属性。

鸭子类型与“继承”和“接口”的对比

特性传统继承/接口 (Java/C++ 风格)鸭子类型 (Python 风格)
检查时机编译期(或代码编写期)运行时
关注点对象是什么is-a 关系)对象能做什么has-a 行为)
代码耦合度较高(必须显式继承父类)极低(只需实现同名方法即可)
灵活性较差,修改类型结构成本高极高,任何对象只要“会叫”就能用

Python 中鸭子类型的实际应用:最典型的例子就是内置函数魔法方法


  • len() 函数:它不要求参数是列表或元组,只要对象内部实现了 __len__() 方法,len() 就能正常工作。


    class MyCollection:
    def __len__(self):
    return 100

    print(len(MyCollection())) # 输出 100,完全合法

    for 循环for 不要求参数是列表,只要对象实现了 __iter__() 方法(即它是可迭代对象),就能进行迭代。(请看高级语法章节)


9.4 抽象类(了解)

抽象类是指包含抽象方法的类,抽象类不能被直接实例化。子类继承抽象类之后,必须重写所有抽象方法。父类定义抽象方法的意义是约定所有子类必须包含某个方法。

python中的抽象类必须满足2个要求:

(1)继承abc.ABC

(2)抽象方法上加装饰器@abstractmethod

class Person(abc.ABC):
    def __init__(self,name,age):
        self.name = name
        self.age = age

    @abc.abstractmethod
    def eat(self):
        pass

    def show(self):
        print(f"姓名:{self.name},年龄:{self.age}")

class Student(Person):
    def eat(self):
        print("吃学生餐")

# p = Person("张三",23) #错误
p = Student("张三",23)
p.show()
p.eat()

9.5 案例:愤怒的小鸟

游戏背景:

在这个模拟的愤怒的小鸟游戏世界里,绿色的小猪偷走了小鸟们的蛋,这引发了小鸟们的愤怒,它们决定展开反击。每只小鸟都具有独特的颜色,并且各自拥有不同的技能,玩家需要操控这些小鸟,利用它们的技能去攻击小猪们建造的各种障碍物,从而达成击败小猪、夺回鸟蛋的目标。

9.5.1 类的设计

1、Birds 基类

1)设计目的

作为所有小鸟类的基类,它定义了小鸟的通用属性和行为,为后续具体小鸟类的扩展提供基础框架,体现了面向对象编程中的抽象和封装思想。

2)属性设计

name:用于标识每只小鸟的名称,方便区分不同个体。

color:代表小鸟的颜色,这是小鸟的一个显著特征,在游戏中可以对应不同类型的小鸟。

skill_description:描述小鸟所具备的独特技能,让玩家了解每只小鸟的特殊能力。

3)方法设计

fly():描述小鸟飞行的基本动作,是小鸟在游戏中的常见行为,所有子类都可以重写该方法来展示不同的飞行特点。

call():模拟小鸟发出叫声的行为,同样可以被子类重写以体现不同小鸟的叫声差异。

use_skill():用于触发小鸟的特殊技能,展示小鸟使用技能的情况,子类可以根据自身技能特点进行相应实现。

2、具体小鸟子类(RedBirds、YellowBirds、BlueBirds)

1)设计目的

继承自 Birds 基类,每个子类代表一种特定颜色的小鸟,它们在继承基类属性和方法的基础上,重写部分方法以展示不同小鸟的独特行为和技能,体现了面向对象编程中的继承和多态思想。

2)属性设计

通过调用基类的 __init__方法,初始化各自的 name、color 和 skill_description 属性,确保每只小鸟都有自己的独特标识和技能。

3)方法设计

fly():重写基类的 fly() 方法,展示不同小鸟的飞行特点,如红鸟以稳定速度飞行,黄鸟快速飞行,蓝鸟优雅飞行。

call():重写基类的 call() 方法,模拟不同小鸟的叫声,增加游戏的趣味性。

3、Obstacle障碍类

1)设计目的

代表游戏中的障碍物,如木头堡垒、石头塔楼等,负责处理障碍物被小鸟攻击的逻辑,与小鸟类进行交互,体现了面向对象编程中的对象交互和封装思想。

2)属性设计

name:标识障碍物的名称,方便区分不同类型的障碍物。

strength:表示障碍物的强度,即它能够承受的伤害值,当强度降为 0 时,障碍物被摧毁。

3)方法设计

be_attacked(bird):模拟障碍物被小鸟攻击的过程,根据小鸟的类型计算伤害,并更新障碍物的强度,同时输出攻击和受损信息,让玩家了解游戏进展。

9.5.2 设计思路

1、Birds 类的方法

1)fly()

作为通用的飞行方法,提供了小鸟飞行的基本描述,子类可以根据自身特点进行个性化实现,以体现不同小鸟的飞行风格。

2)call()

模拟小鸟发出叫声的行为,为游戏增加生动性,子类可以重写该方法来展示不同小鸟的叫声特点。

3)use_skill()

用于触发小鸟的特殊技能,通过输出技能描述,让玩家了解小鸟使用技能的情况,不同子类可以根据自身技能进行不同的实现。

2、具体小鸟子类的方法

fly() 和 call():重写基类的方法,根据不同小鸟的特点进行个性化实现,展示不同小鸟的飞行和叫声差异,体现了多态性。

3、Obstacle 类的方法

be_attacked(bird):接收一个小鸟对象作为参数,根据小鸟的类型计算伤害,并更新障碍物的强度。通过判断障碍物的强度是否小于等于 0,输出障碍物是否被摧毁的信息,实现了障碍物与小鸟之间的交互逻辑。

通过这样的类和方法设计,整个游戏模拟程序具有良好的扩展性和可维护性,方便后续添加更多类型的小鸟和障碍物,以及实现更复杂的游戏逻辑。

9.5.3 代码实现

"""
    愤怒的小鸟
"""
# 定义鸟类基类
class Birds:
    def __init__(self, name, color, skill_description):
        self.name = name
        self.color = color
        self.skill_description = skill_description

    def fly(self):
        print(f"{self.name} 正在飞行...")

    def call(self):
        print(f"{self.name} 发出叫声...")

    def use_skill(self):
        print(f"{self.name} 使用了技能:{self.skill_description}")

# 定义红鸟子类
class RedBirds(Birds):
    def __init__(self):
        super().__init__("红火", "红色", "撞击前方障碍物,造成大量伤害")

    def fly(self):
        print("红火以稳定的速度向前飞行...")

    def call(self):
        print("红火发出 'wei呀....' 的叫声")

# 定义黄鸟子类
class YellowBirds(Birds):
    def __init__(self):
        super().__init__("小黄", "黄色", "瞬间加速,穿透薄障碍物")

    def fly(self):
        print("小黄快速向前飞行...")

    def call(self):
        print("小黄发出 '啾啾啾....' 的叫声")

# 定义蓝鸟子类
class BlueBirds(Birds):
    def __init__(self):
        super().__init__("小蓝", "蓝色", "分裂成三只小鸟,分散攻击")

    def fly(self):
        print("小蓝优雅地向前飞行...")

    def call(self):
        print("小蓝发出 '叽叽叽....' 的叫声")

# 定义障碍物类
class Obstacle:
    def __init__(self, name, strength):
        self.name = name
        self.strength = strength

    def be_attacked(self, bird):
        print(f"{bird.name} 冲向了 {self.name}")
        bird.use_skill()
        if isinstance(bird, RedBirds):
            damage = 80
        elif isinstance(bird, YellowBirds):
            damage = 50
        elif isinstance(bird, BlueBirds):
            damage = 30 * 3  # 分裂成三只,每只造成 30 点伤害
        self.strength -= damage
        if self.strength <= 0:
            print(f"{self.name} 被摧毁了!")
        else:
            print(f"{self.name} 还剩余 {self.strength} 点强度")

# 模拟游戏过程
if __name__ == "__main__":
    # 创建不同颜色的小鸟
    red_bird = RedBirds()
    yellow_bird = YellowBirds()
    blue_bird = BlueBirds()

    # 创建障碍物
    obstacle1 = Obstacle("木头堡垒", 100)
    obstacle2 = Obstacle("石头塔楼", 200)

    # 红鸟攻击木头堡垒
    obstacle1.be_attacked(red_bird)

    # 黄鸟攻击石头塔楼
    obstacle2.be_attacked(yellow_bird)

    # 蓝鸟攻击石头塔楼
    obstacle2.be_attacked(blue_bird)

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部