Python面向对象(九)

什么是面向对象

面向对象(OOP)是一种编程思想。核心理念是:

现实世界万物皆对象,每个对象都有“属性 + 行为”。
在程序中,我们用“类”作为现实事物的设计图,用“对象”作为根据图纸生产出的实体,由对象执行实际工作。


现实世界 → 程序世界 的映射

PDF 第 1 页中提供了一个总览图(已文字化):

现实事物
 ├─ 属性(如:重量、大小、价格…)
 └─ 行为(如:运行、响铃、打印…)

程序中对应为:
类(设计图)
 ├─ 成员变量(属性)
 └─ 成员方法(行为)

通过类定义事物的“属性 + 行为”,再基于类批量生产对象。

例如 PDF 图示中的闹钟:

类:Clock
属性:颜色、价格
行为:计时、响铃
对象:小明的闹钟、小红的闹钟、我的闹钟…

类与对象的关系

PDF 图片中展示了一个非常关键的思想:

类(设计图) → 生产 → 对象(实例)

类只负责描述结构,不负责实际运行;
对象才是真正“干活”的实体。

就像现实中不会直接使用“图纸上的闹钟”,
你必须“生产(实例化)”一个闹钟出来才能使用。


示例:用类作为设计图创建闹钟对象

来自 PDF(第 1 页)示例逻辑文字化:

class Clock:
    color = None
    price = None
    
    def ring(self):
        print("闹钟响铃中...")

# 创建三个闹钟对象
clock1 = Clock()
clock2 = Clock()
clock3 = Clock()

图示中展示:

  • 每个对象都独立存在
  • 虽然都来自同一个类,但它们的属性值可以不同

这就是“面向对象”的核心优势:批量生产相似事物,但可以拥有不同属性。


程序类比现实制造流程

现实制造流程:

① 设计图纸 → ② 分析属性与功能 → ③ 批量生产物品

程序对应:

① 定义类 → ② 设计属性与方法 → ③ 批量创建对象

因此:

类是抽象的设计图;对象是具体可使用的实体。


总结一句话

面向对象就是:用类描述现实世界,用对象模拟真实事物。
类定义“是什么”,对象负责“能做什么”。


类与对象

在面向对象编程中:

  • 类(class)是设计图
  • 对象(object)是根据设计图生产出来的实例

类中可以包含两类成员:

  • 成员变量(属性)
  • 成员方法(行为)

类的定义语法

PDF 给出的基本格式:

class 类名:
    代码块

例如:

class Student:
    name = None
    age = None

    def say_hi(self):
        print(f"大家好,我是{self.name},今年{self.age}岁")

对象的创建(实例化)

语法:

对象名 = 类名()

示例:

stu_1 = Student()
stu_2 = Student()

PDF 图示:

Student 类是图纸;
stu_1、stu_2 是从图纸制造出的两个对象;
它们的数据互不影响。

对象属性(成员变量)

在类中定义的变量 → 称为成员变量(属性)

示例:

class Student:
    name = None
    age = None

stu_1 = Student()
stu_1.name = "观止"
stu_1.age = 20

print(stu_1.name)
print(stu_1.age)

输出:

观止
20

每个对象都有自己独立的属性存储空间。


成员方法(类的方法)

方法本质上是定义在类内部的函数。

示例:

class Student:

    def say_hi(self):
        print("大家好,我是学生")

调用方法:

stu = Student()
stu.say_hi()

输出:

大家好,我是学生

self 的含义(非常核心)

self 就是“对象本身”的引用。
哪个对象调用方法,self 就指向哪个对象。

示例:

def say_hi(self):
    print(f"我是{self.name}")

如果 stu_1 调用:

stu_1.say_hi()  
→ self === stu_1

如果 stu_2 调用:

stu_2.say_hi()
→ self === stu_2

这就是 self 的本质。


构造方法(init)

PDF 第 4 页:

构造方法用于在“对象创建时自动执行初始化操作”。

格式:

def __init__(self, 参数...):
    初始化代码

示例:

class Student:
    def __init__(self, name, age):
        print("我是构造方法,我自动执行")
        self.name = name
        self.age = age

stu = Student("观止", 20)

输出:

我是构造方法,我自动执行

说明:

  • 构造函数自动执行
  • 适合做初始化赋值
  • 不需要手动调用

构造方法的价值

构造方法让代码更优雅:

无构造方法:

stu = Student()
stu.name = "观止"
stu.age = 20

有构造方法:

stu = Student("观止", 20)

更加简洁、规范、稳定。


构造方法与成员方法对比

特点构造方法 __init__成员方法
调用时间对象创建时自动执行由对象手动调用
作用初始化属性完成功能
是否必须包含 self

完整示例

class Student:

    def __init__(self, name, age):
        print("构造方法执行了")
        self.name = name
        self.age = age

    def say_hi(self):
        print(f"大家好,我是{self.name},今年{self.age}岁")

stu = Student("观止", 20)
stu.say_hi()

输出:

构造方法执行了
大家好,我是观止,今年20岁

总结

  • 类:现实事物的抽象
  • 对象:类的实例
  • 属性:对象的数据
  • 方法:对象的行为
  • self:指向调用方法的对象
  • 构造方法:创建对象时初始化数据

类的魔术方法(Magic Methods)

魔术方法是 Python 内置的一类特殊方法,以 双下划线 __ 开头和结尾
它们并不会被我们手动调用,而是由 Python 在特定时机自动触发。

PDF 展示了三类高频魔术方法:

  • __str__ —— 控制对象打印输出的内容
  • __lt__ —— 定义对象的 < 比较方式
  • __le__ —— 定义对象的 <= 比较方式
  • __eq__ —— 定义对象的 == 判断方式

这些方法让对象更易读、更易比较,非常重要。


str:控制对象的字符串输出

默认情况下:

stu = Student("观止", 20)
print(stu)

会输出类似:

<__main__.Student object at 0x00000213A3E2F880>

这是对象的内存地址,不具可读性。


通过 __str__ 美化输出

PDF 示例(第 6 页):

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"name:{self.name}, age:{self.age}"

stu = Student("观止", 20)
print(stu)

输出变为:

name:观止, age:20

更方便调试与日志记录。


对象比较问题(为什么需要 lt / le / eq)

在没有魔术方法前:

stu1 < stu2

会报错,因为对象不能直接比较大小。

PDF 图片(第 7 页)展示了错误信息文字化如下:

TypeError: '<' not supported between instances of 'Student' and 'Student'

因此,我们需要重写比较相关的魔术方法。


lt:定义对象的 <(小于)规则

示例(PDF 第 7 页):

class Student:
    def __init__(self, age):
        self.age = age

    def __lt__(self, other):
        return self.age < other.age

stu1 = Student(18)
stu2 = Student(20)
print(stu1 < stu2)   # True

输出:

True

le:定义对象的 <=(小于等于)规则

def __le__(self, other):
    return self.age <= other.age

示例(PDF):

Student(20) <= Student(20) → True
Student(18) <= Student(20) → True

eq:定义对象的 ==(相等)规则

默认的 == 是判断内存地址是否相等,而不是数据是否一致。

可重写:

def __eq__(self, other):
    return self.age == other.age

PDF 示例(第 8 页):

stu1 = Student(18)
stu2 = Student(18)

print(stu1 == stu2)

输出:

True

PDF 完整示例

class Student:
    def __init__(self, age):
        self.age = age

    def __lt__(self, other):
        return self.age < other.age

    def __le__(self, other):
        return self.age <= other.age

    def __eq__(self, other):
        return self.age == other.age

s1 = Student(18)
s2 = Student(20)
s3 = Student(18)

print(s1 < s2)   # True
print(s1 <= s3)  # True
print(s1 == s3)  # True

输出:

True
True
True

魔术方法总结

魔术方法触发条件用途
__str__print(obj) / str(obj)控制对象输出内容
__lt__obj1 < obj2定义“小于”规则
__le__obj1 <= obj2定义“小于等于”规则
__eq__obj1 == obj2定义“相等”规则

封装(Encapsulation)

封装是面向对象三大核心特性之一,其目标是:

把数据(属性)和行为(方法)装进一个类中,并通过权限控制隐藏内部实现,只暴露必要的接口给外部使用。

封装能让程序更安全、模块更清晰,并防止外部任意修改对象内部数据。


成员变量与成员方法(封装的基础)

类中定义的:

  • 变量 → 成员变量(属性)
  • 函数 → 成员方法(行为)

对象通过点语法访问:

stu = Student()
stu.name = "观止"
stu.say_hi()

但是,直接暴露属性存在风险,例如随意改年龄、非法赋值等。
因此需要“封装”来保护数据。


私有成员(前置双下划线)

Python 提供“私有属性”和“私有方法”机制:
只需在名称前加上 双下划线 __

格式:

class Student:
    __age = None        # 私有属性
    def __secret(self): # 私有方法
        ...

特点:

  • 私有成员 不能在类外部直接访问
  • 只能在类内部使用
  • 对象外部访问会报错

示例:

class Student:
    def __init__(self, age):
        self.__age = age

stu = Student(20)
print(stu.__age)  # 报错

私有化能够避免外部随意修改关键数据。


私有属性与安全访问(Getter / Setter 方法)

为了既保护数据,又能让外部安全访问,我们一般提供:

  • getter(获取属性)
  • setter(设置属性)

示例:

class Student:
    def __init__(self, age):
        self.__age = age

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if 0 < age < 150:
            self.__age = age
        else:
            print("年龄不合法")

stu = Student(20)
print(stu.get_age())   # 20
stu.set_age(25)        # 合法
stu.set_age(-9)        # 不合法

好处:

  • 外部无法直接改动内部数据
  • setter 可做合法性检查(如年龄不能为负)
  • 程序更加稳定、安全

私有方法(隐藏内部逻辑)

有些方法只用于类内部的逻辑,不希望被外部调用,例如内部运算流程。

class Student:
    def __secret(self):
        print("这是内部逻辑")

    def public_method(self):
        print("调用公开方法")
        self.__secret()  # 内部调用

外部调用:

stu.public_method()
stu.__secret()  # 报错

封装的意义:
只暴露必要的功能入口,把不希望外界操作的逻辑隐藏在类内部。


封装的价值总结

作用描述
保护数据防止外部非法修改数据
简化使用外界不需要知道内部逻辑,只需调用公开方法
降低耦合内部变化不影响外部使用
信息隐藏让系统更安全、更稳定

一句话总结:

封装就是把复杂的实现隐藏起来,把简单的接口暴露出来。


继承(Inheritance)

继承是面向对象三大特性之一,它让 一个类可以拥有另一个类的属性与方法,从而实现代码复用、结构优化与扩展。

核心思想:

父类提供能力,子类继承并拓展能力。


为什么需要继承

没有继承时,多个类中如果有重复代码(如相同的属性、相同的方法),必须在每个类中重复编写。

继承可以解决:

  • 重复代码过多
  • 类之间结构混乱
  • 扩展性差

继承让我们可以像下面这样思考:

动物(父类)
 ├─ 属性:名字、年龄
 └─ 行为:吃、喝

 ↓ 继承

子类:狗、猫
 ├─ 继承动物的属性与行为
 └─ 还可以增加自己的行为(如狗会摇尾巴)

继承的基本语法

class 子类名(父类名):
    pass

示例:

class Animal:
    def eat(self):
        print("动物会吃")

class Dog(Animal):
    pass

d = Dog()
d.eat()

输出:

动物会吃

说明:

  • Dog 什么都没写,但却拥有 Animal 的方法
  • 这就是继承带来的复用能力

子类可以拥有自己的方法

继承并不会限制扩展:

class Dog(Animal):
    def bark(self):
        print("汪汪汪")

d = Dog()
d.eat()
d.bark()

结果:

动物会吃
汪汪汪

方法重写(Override)

当父类的方法不能满足子类需求时,子类可以 重写(覆盖) 父类的方法。

示例:

class Animal:
    def speak(self):
        print("动物叫")

class Dog(Animal):
    def speak(self):
        print("狗叫:汪汪汪")

Dog().speak()

输出:

狗叫:汪汪汪

重写后的方法会取代父类方法。


调用父类方法:super()

有时我们希望在子类中扩展父类行为,而不是完全替代:

class Animal:
    def speak(self):
        print("动物叫")

class Dog(Animal):
    def speak(self):
        super().speak()     # 调用父类方法
        print("狗叫:汪汪汪")

Dog().speak()

输出:

动物叫
狗叫:汪汪汪

说明:

  • super() 是 Python 推荐的父类方法调用方式
  • 保持继承结构更加稳定

父类的属性与构造方法继承

当父类中有 __init__ 构造方法时,子类默认不继承它。
如果子类中没有定义 __init__,则对象创建时不会执行父类构造。

因此,当子类需要父类构造时,必须手动调用:

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

class Dog(Animal):
    def __init__(self, name, age):
        super().__init__(name)  # 调用父类构造
        self.age = age

d = Dog("小黑", 3)
print(d.name, d.age)

输出:

小黑 3

多继承(Multiple Inheritance)

Python 允许一个类同时继承多个父类:

class A:
    def funcA(self):
        print("A功能")

class B:
    def funcB(self):
        print("B功能")

class C(A, B):
    pass

c = C()
c.funcA()
c.funcB()

输出:

A功能
B功能

多继承的特点:

  • 子类可以获得多个父类的能力
  • 若多个父类存在同名方法,则根据 MRO(方法解析顺序) 决定调用哪个

解析顺序可查看:

print(C.mro())

继承的优势总结

优势描述
代码复用子类自动获得父类能力,减少重复代码
结构清晰按“共性 → 个性”组织类
易于扩展新子类轻松继承并扩展功能
支持多态继承是多态的基础

一句话总结:

继承让类之间形成体系,让代码更有层次、更易维护。


多态(Polymorphism)

多态是面向对象的三大特性之一,它的核心思想是:

同一个方法名,在不同对象上具有不同的表现形式。

换句话说:

  • 调用哪个方法,由对象本身决定
  • 不是由调用方式决定
  • 也不是由变量名决定

这种机制让代码更加灵活、可扩展。


多态的核心概念

多态通常基于继承与方法重写实现。

基础关系如下:

父类:提供通用方法
子类:重写父类方法
调用者:只管调用,不关心对象的具体类型

示意文字化:

动物.speak()   → 结果依赖具体动物种类
狗.speak()     → 汪汪汪
猫.speak()     → 喵喵喵

虽然都是 speak() 方法,但输出完全不同,这就是多态。


多态示例

class Animal:
    def speak(self):
        print("动物叫")

class Dog(Animal):
    def speak(self):
        print("狗叫:汪汪汪")

class Cat(Animal):
    def speak(self):
        print("猫叫:喵喵喵")

无论是谁调用 speak(),结果不同:

def make_noise(obj):
    obj.speak()

make_noise(Dog())   # 狗叫
make_noise(Cat())   # 猫叫

输出:

狗叫:汪汪汪
猫叫:喵喵喵

这里 make_noise 函数不关心 obj 是什么类型,只要对象有 speak 方法就能正常使用。


多态的价值

价值描述
统一调用方式调用方不需要区分对象类型
易扩展新增子类无需修改旧逻辑
可替换性父类引用可以指向任何子类对象
高内聚低耦合结构更合理、灵活性更高

举例说明:

如果你后续新增一个 Bird 类,只需:

class Bird(Animal):
    def speak(self):
        print("鸟叫:叽叽喳喳")

make_noise 函数无需任何修改:

make_noise(Bird())

即可正常使用。这就是多态的强大之处。


多态常与“鸭子类型”结合

Python 是动态语言,遵循 鸭子类型(Duck Typing)

“如果一只鸟看起来像鸭子、叫起来像鸭子、走路像鸭子,那么它就是鸭子。”

也就是说:

  • 不要求对象必须继承同一个父类
  • 只要有同名方法,也能实现多态效果

示例:

class Person:
    def speak(self):
        print("人类交流")

class Dog:
    def speak(self):
        print("狗叫")

def make_noise(obj):
    obj.speak()

make_noise(Person())
make_noise(Dog())

输出仍然正常:

人类交流
狗叫

在 Python 中,多态不仅基于继承,也基于行为一致性。


多态的总结

  • 多态让 “同一操作,不同对象有不同结果”
  • 依赖于方法重写
  • 使代码调用方无需感知对象具体类型
  • 提高可扩展性、灵活性,是大型项目必备能力
  • Python 多态更灵活,因为支持鸭子类型,只要对象有同名方法即可

一句话记住多态:

调用方只管调用方法,不关心对象是谁;对象自己决定如何执行。


综合案例:面向对象完整开发流程

本案例通过“宠物店系统”的场景,演示面向对象的全流程:

  1. 分析对象(事物)
  2. 抽取属性与行为
  3. 定义类
  4. 编写构造方法
  5. 创建对象
  6. 运用继承与多态优化结构

这是面向对象思想的完整闭环。


步骤一:分析现实对象

宠物店中的动物具有:

  • 属性:名字、年龄、颜色
  • 行为:吃东西、叫、玩耍

不同动物之间还有差异:

  • 狗:会叫 “汪汪汪”
  • 猫:会叫 “喵喵喵”

因此适合使用继承结构:

Animal(父类)
 ├─ Dog(子类)
 └─ Cat(子类)

步骤二:抽取属性与行为

所有动物共有的属性:

  • name
  • age
  • color

共有的行为:

  • speak() —— 叫
  • eat() —— 吃东西

不同动物的行为需要重写 speak() 方法(多态实现)。


步骤三:定义父类 Animal

class Animal:

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

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

    def speak(self):
        print("动物叫")

说明:

  • 构造方法初始化属性
  • speak 方法后续允许子类重写
  • eat 方法所有子类直接复用

步骤四:定义子类 Dog 与 Cat(继承 + 多态)

class Dog(Animal):

    def speak(self):
        print(f"{self.name}:汪汪汪!")

class Cat(Animal):

    def speak(self):
        print(f"{self.name}:喵喵喵!")

说明:

  • Dog 与 Cat 继承 Animal
  • 只重写 speak 方法实现差异化
  • eat() 和属性完全复用父类

步骤五:创建对象并体验完整 OOP 流程

dog = Dog("小黑", 3, "黑色")
cat = Cat("花花", 2, "白色")

dog.eat()
dog.speak()

cat.eat()
cat.speak()

输出:

小黑 在吃东西
小黑:汪汪汪!
花花 在吃东西
花花:喵喵喵!

这里展示了:

  • 构造方法自动初始化属性
  • 父类方法 eat() 被完全复用
  • 子类方法 speak() 各自不同,实现多态

步骤六:加入统一接口(多态的威力)

def pet_show(animal):
    animal.speak()
    animal.eat()

调用:

pet_show(dog)
pet_show(cat)

输出仍然正确:

小黑:汪汪汪!
小黑 在吃东西
花花:喵喵喵!
花花 在吃东西

说明:

  • pet_show 不关心 animal 是 Dog 还是 Cat
  • 这就是多态带来的统一接口与替换能力

综合案例关键点总结

技术点本案例中的体现
封装属性和方法被封装在 Animal 类内部
构造方法初始化 name、age、color
继承Dog 与 Cat 复用 Animal 的属性与行为
方法重写子类重新定义 speak()
多态pet_show() 接受任何 Animal 子类对象

一句话总结:

面向对象不是语法,而是一种思考方式。
本案例展示了使用类 → 对象 → 封装 → 继承 → 多态构建可扩展程序的流程。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇