什么是面向对象
面向对象(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 多态更灵活,因为支持鸭子类型,只要对象有同名方法即可
一句话记住多态:
调用方只管调用方法,不关心对象是谁;对象自己决定如何执行。
综合案例:面向对象完整开发流程
本案例通过“宠物店系统”的场景,演示面向对象的全流程:
- 分析对象(事物)
- 抽取属性与行为
- 定义类
- 编写构造方法
- 创建对象
- 运用继承与多态优化结构
这是面向对象思想的完整闭环。
步骤一:分析现实对象
宠物店中的动物具有:
- 属性:名字、年龄、颜色
- 行为:吃东西、叫、玩耍
不同动物之间还有差异:
- 狗:会叫 “汪汪汪”
- 猫:会叫 “喵喵喵”
因此适合使用继承结构:
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 子类对象 一句话总结:
面向对象不是语法,而是一种思考方式。
本案例展示了使用类 → 对象 → 封装 → 继承 → 多态构建可扩展程序的流程。