Python 面向对象编程:从“过程清单”到“智能积木”的思维革命

Python 面向对象编程:从"过程清单"到"智能积木"的思维革命

作者:书到用时方恨少!

发布日期:2026年4月15日

阅读时长:约30分钟

📌 前言

嗨,大家好,我是书到用时方恨少!。今天我们不聊具体的库,也不钻算法的牛角尖,我们来谈谈 Python 中那个让很多自学者从入门到放弃,又从放弃到"真香"的概念------面向对象编程(Object-Oriented Programming,OOP)

你是否有过这样的困惑:写着写着脚本,代码量一上来,满屏都是 global 变量和散装的函数,改一个地方牵一发而动全身?想用别人写好的 Django 模型或者 PyTorch 的网络层,看着满眼的 classself__init__,心里直犯怵?

别慌。这篇博客的目标,就是帮你把脑子里那团关于"面向对象"的迷雾拨开。我会用遥控器、智能冰箱、积木块这些接地气的比喻,从最底层的思维差异讲起,一直到你能自己动手搭一个五脏俱全的小项目。读完这篇,你会发现:原来面向对象不是故意把简单事情复杂化的玄学,而是帮你把复杂事情变简单的神器。


1. 🧠 思维大对决:指令清单 vs 智能团队

在写代码之前,我们得先搞清楚一个根本问题:面向过程(Procedure-Oriented Programming)和面向对象,到底差在哪?

1.1 面向过程:一行一行的"行动指南"

想象你要举办一场小型家庭音乐会。如果用面向过程的思维来组织,你的大脑会列出一张长长的清单:

  1. 搬出音响,插上电源。
  2. 打开功放。
  3. 把吉他接上线。
  4. 调整音量到 50%。
  5. 开始弹唱。

写成代码大概是这样:

python 复制代码
# 面向过程风格:散装的动作
speaker = "音响"
amplifier = "功放"
guitar = "吉他"

def setup_device(device):
    print(f"把{device}摆好,插电")

def play_instrument(instrument, volume):
    print(f"用{instrument}弹奏,音量{volume}")

setup_device(speaker)
setup_device(amplifier)
play_instrument(guitar, 50)

优点:简单直接,脑子不用转弯,小任务特别顺手。

痛点 :一旦音乐会规模变大,来了贝斯手、键盘手,甚至还有一个调音师,你的清单就会爆炸。更麻烦的是,如果音响坏了要换一种型号,你不仅要改 setup_device 里面的逻辑,还得祈祷没有人直接去戳 speaker 这个变量。

数据和操作数据的行为是割裂的。 就像你把鸡蛋液放在一个碗里,把碗放在了邻居家,回头自己忘了。

1.2 面向对象:各司其职的"智能团队"

还是那场音乐会,但这次你换了个思路。你不亲自指挥每一个动作,而是把任务分配给了几个聪明的个体(对象)

  • 音响对象:它自己知道怎么开机、怎么调音、怎么关机。你只需要按它身上的"播放"按钮。
  • 吉他手对象:他知道怎么调弦、怎么弹奏。你只需要对他说:"来一段《加州旅馆》前奏"。

写成代码,味道就变了:

python 复制代码
# 面向对象风格:封装好的智能个体
class Speaker:
    def __init__(self, name):
        self.name = name
        self.is_on = False
    
    def power_on(self):
        self.is_on = True
        print(f"{self.name} 已开机,嗡------")

class Guitarist:
    def __init__(self, name):
        self.name = name
    
    def play(self, song):
        print(f"{self.name} 拿起吉他,弹奏《{song}》")

# 使用
yamaha_speaker = Speaker("Yamaha HS8")
eric = Guitarist("Eric")

yamaha_speaker.power_on()
eric.play("Hotel California")

注意到了吗?语法从 动词(名词) 变成了 名词.动词() 。数据(音响的开关状态、吉他手的名字)和操作数据的方法(开机、弹奏)被捆绑 在了一起。这就是面向对象最底层的思维转变------封装的雏形。


2. 📦 封装:给你的电视套上外壳,只留几个按钮

封装是面向对象三大特征(封装、继承、多态)中最基础也最重要的一环。不理解封装,你就无法理解为什么要有类。

2.1 没有封装的世界:电路板裸奔

想象一下,如果电视机没有外壳,你看到的是这样的景象:裸露的电容、发烫的芯片、复杂的走线。你想调高音量,得拿根导线去戳第38号针脚。

这日子没法过!

所以工程师做了两件事:

  1. 套个外壳:把复杂的内部结构藏起来。
  2. 留几个按钮:开关、音量加减、频道切换。

外壳 + 按钮 = 封装。

2.2 Python 里的封装:私有属性 + 公开方法

在 Python 中,类(Class)就是那个外壳。我们把数据(属性)藏在外壳里面,只暴露出有限的接口(方法)给外部使用。

python 复制代码
class Television:
    def __init__(self, brand):
        self.brand = brand          # 公开属性:谁都可以看
        self.__volume = 10          # 私有属性:前面加两个下划线,藏起来了!
        self.__is_on = False        # 私有属性:藏起来了!
    
    # 公开方法:给用户的"按钮"
    def press_power(self):
        self.__is_on = not self.__is_on
        if self.__is_on:
            print(f"{self.brand} 电视屏幕亮了")
        else:
            print("啪,黑屏了")
    
    def volume_up(self):
        if self.__is_on:
            if self.__volume < 100:
                self.__volume += 1
                print(f"当前音量:{self.__volume}")
        else:
            print("电视还没开呢,按个锤子")
    
    def get_current_volume(self):
        # 这也是封装:我可以在这里加个日志记录谁看了音量
        return self.__volume

2.3 为什么封装能救命?------ 以银行账户为例

假设你写了一个银行账户类,没有封装:

python 复制代码
class BadBankAccount:
    def __init__(self, balance):
        self.balance = balance  # 余额直接暴露

acc = BadBankAccount(1000)
acc.balance = -50000  # 直接修改!银手镯警告!

余额怎么能直接赋值呢?必须走存款/取款的逻辑,要有日志、要有权限检查、要保证余额不能为负。

使用封装后的正确姿势:

python 复制代码
class GoodBankAccount:
    def __init__(self, initial):
        self.__balance = initial  # 余额设为私有
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"入账 {amount},当前余额 {self.__balance}")
            return True
        print("金额无效")
        return False
    
    def get_balance(self):
        # 想查余额?走我这扇门,我可以记录谁查了账
        return self.__balance

acc = GoodBankAccount(1000)
# acc.__balance  # 报错!不能直接访问
acc.deposit(200) # 只能通过安全通道

封装的核心思想把数据藏起来,通过方法控制访问 。内部电路怎么改(比如换一块三星屏)无所谓,只要按钮(volume_up)的功能不变,用遥控器的人(调用者)一行代码都不用改。


3. 🧱 面向对象三大特征速览

在深入代码细节之前,我们先快速浏览一下面向对象的三大法宝。这里只是简述,后续会有专题详解。

特征 一句话解释 生活类比
封装 把数据和操作数据的方法包在一起,隐藏内部细节,只暴露有限接口。 电视机的塑料壳和按钮。
继承 一个类可以继承另一个类的属性和方法,并在此基础上扩展。 你是你爸妈的孩子,继承了他们的一部分特征和财产。
多态 同一个方法名,不同的对象可以有不同的响应方式。 "叫"这个动作,狗是"汪汪",猫是"喵喵"。

本篇我们重点攻克面向对象的基础语法。封装会稍微提一些,至于更详细的封装的使用,以及继承和多态的详细讲解,我会在后续博客中展开。


4. 📐 如何定义一个类------从零开始造积木

4.1 基本语法

python 复制代码
class ClassName:
    """类的文档字符串(可选)"""
    
    # 类属性(所有实例共享)
    category = "电子设备"
    
    # 初始化方法(构造函数)
    def __init__(self, param1, param2):
        self.attr1 = param1   # 实例属性
        self.attr2 = param2   # 实例属性
    
    # 普通方法
    def do_something(self):
        print(f"正在使用 {self.attr1}")
  • class 关键字后面跟类名,类名通常采用大驼峰命名法 (每个单词首字母大写,如 SmartPhone)。
  • __init__ 是一个特殊的魔法方法,在创建对象时自动调用,用来初始化对象的属性。
  • self 代表实例本身,我们马上会详细解释。

4.2 创建实例并使用

python 复制代码
# 创建实例(调用类名,像调用函数一样)
my_tv = Television("Sony")

# 调用方法
my_tv.press_power()   # Sony 电视屏幕亮了
my_tv.volume_up()     # 当前音量:11

# 访问公开属性
print(my_tv.brand)    # Sony

5. 🔑 灵魂关键词:self 到底是谁?

很多初学者卡在 self 上。其实 self 一点也不神秘,它就是对象本身的代号。

python 复制代码
class Dog:
    def __init__(self, name):
        self.name = name   # self.name 是这个实例的 name 属性
    
    def bark(self):
        print(f"{self.name} 汪汪叫!")   # 这里的 self.name 就是上面那个

dog_a = Dog("旺财")
dog_b = Dog("来福")

dog_a.bark()   # 输出:旺财 汪汪叫!
dog_b.bark()   # 输出:来福 汪汪叫!

当你写 dog_a.bark() 时,Python 在背后偷偷做了转换:

  • 你写的:dog_a.bark()
  • Python 执行的:Dog.bark(dog_a)

self 就是 dog_a 本身。它告诉方法:"嘿,现在是哪个具体的狗在叫?"

小贴士self 不是 Python 的关键字,你完全可以写成 thisme。但按照 Python 社区的约定,请务必使用 self,否则会被同行打死。


6. 📦 属性和方法:类的两大核心构件

6.1 属性:对象有什么

属性就是数据,描述对象的状态。

python 复制代码
class Student:
    school = "第一中学"   # 类属性:所有学生共享,用类名访问
    
    def __init__(self, name, age):
        self.name = name    # 实例属性:每个学生独有的名字
        self.age = age      # 实例属性:每个学生独有的年龄

# 访问实例属性
s1 = Student("小明", 15)
print(s1.name)   # 小明

# 访问类属性
print(Student.school)   # 第一中学
print(s1.school)        # 也可以通过实例访问(不推荐)
  • 实例属性 :每个对象独有的数据,在 __init__ 中通过 self.xxx 定义。
  • 类属性:所有对象共享的数据,直接写在类体里。

6.2 方法:对象能做什么

方法就是行为 ,定义在类里面的函数,第一个参数必须是 self

python 复制代码
class Calculator:
    def __init__(self, brand):
        self.brand = brand
        self.__history = []   # 私有属性,记录历史
    
    def add(self, a, b):
        result = a + b
        self.__log(f"add({a}, {b}) = {result}")
        return result
    
    def __log(self, msg):     # 私有方法:只能在类内部调用
        self.__history.append(msg)
    
    def show_history(self):
        for entry in self.__history:
            print(entry)

calc = Calculator("卡西欧")
print(calc.add(3, 5))    # 8
calc.show_history()      # add(3, 5) = 8
# calc.__log("test")     # 报错!私有方法不能外部调用
  • 公开方法:对外提供的接口。
  • 私有方法 :前面加 __,只能在类内部使用,外部无法调用。用于隐藏实现细节。

7. ✨ 常用魔法方法:让类活起来

魔法方法(Magic Methods)是以双下划线开头和结尾的特殊方法。它们会在特定时机被 Python 自动调用,让你的类拥有"超能力"。

魔法方法 触发时机 作用
__init__ 创建对象时 初始化对象,设置初始属性
__str__ print(obj)str(obj) 返回用户友好的字符串表示,用于给人看
__repr__ 直接在交互环境输入对象名 返回开发者友好的字符串表示,用于调试,通常能通过 eval 还原对象
__len__ len(obj) 返回对象的"长度"
__call__ obj() 让实例可以像函数一样被调用
__eq__ obj1 == obj2 定义等于比较的逻辑
__getitem__ obj[key] 让对象支持索引访问
__enter__ / __exit__ with obj: 上下文管理器,用于资源管理(如文件打开关闭)

7.1 __str____repr__ 示例

python 复制代码
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
    
    def __str__(self):
        return f"《{self.title}》 by {self.author}"
    
    def __repr__(self):
        return f"Book('{self.title}', '{self.author}')"

book = Book("三体", "刘慈欣")
print(book)        # 《三体》 by 刘慈欣  (调用了 __str__)
print(repr(book))  # Book('三体', '刘慈欣')  (调用了 __repr__)

7.2 __call__ 让对象变函数

python 复制代码
class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    
    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
print(double(5))   # 10,就像调用函数一样!

7.3 __len____getitem__ 自定义容器

python 复制代码
class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []
    
    def add_song(self, song):
        self.songs.append(song)
    
    def __len__(self):
        return len(self.songs)
    
    def __getitem__(self, index):
        return self.songs[index]

my_list = Playlist("开车专用")
my_list.add_song("一路向北")
my_list.add_song("平凡之路")

print(len(my_list))       # 2
print(my_list[0])         # 一路向北
for song in my_list:      # 支持迭代!
    print(song)

8. 🔨 综合实战:打造一个智能"待办事项"管理器

理论讲了这么多,我们来动动手,把上面学到的知识点串起来。我们要写一个待办事项管理器 TaskManager,它具备以下功能:

  1. 可以添加任务,每个任务有描述和优先级。
  2. 可以标记任务完成。
  3. 可以列出所有未完成任务,按优先级排序。
  4. 可以查看任务总数和已完成数量。
  5. 使用封装保护内部数据结构。

8.1 代码实现

python 复制代码
class Task:
    """单个待办事项的类"""
    def __init__(self, description, priority=1):
        self.description = description
        self.priority = priority
        self.completed = False
    
    def mark_done(self):
        self.completed = True
    
    def __str__(self):
        status = "✅" if self.completed else "⏳"
        return f"[{status}] {self.description} (优先级: {self.priority})"
    
    def __repr__(self):
        return f"Task('{self.description}', {self.priority})"


class TaskManager:
    """待办事项管理器,封装了对任务列表的所有操作"""
    def __init__(self, owner):
        self.owner = owner
        self.__tasks = []   # 私有属性,外部不能直接操作
    
    def add_task(self, description, priority=1):
        """添加任务"""
        if priority < 1 or priority > 5:
            raise ValueError("优先级必须在 1~5 之间")
        new_task = Task(description, priority)
        self.__tasks.append(new_task)
        print(f"已添加任务:{new_task}")
    
    def finish_task(self, index):
        """根据索引完成任务"""
        if 0 <= index < len(self.__tasks):
            self.__tasks[index].mark_done()
            print(f"任务已完成:{self.__tasks[index].description}")
        else:
            print("无效的任务索引")
    
    def get_pending_tasks(self):
        """获取所有未完成的任务,并按优先级降序排列"""
        pending = [t for t in self.__tasks if not t.completed]
        # 按优先级从高到低排序(数字越大优先级越高)
        return sorted(pending, key=lambda t: t.priority, reverse=True)
    
    def show_pending(self):
        """打印所有待办事项"""
        pending = self.get_pending_tasks()
        if not pending:
            print(f"{self.owner},太棒了!没有待办任务!")
            return
        print(f"\n{self.owner} 的待办清单(按优先级排序):")
        for i, task in enumerate(pending):
            print(f"  {i}. {task}")
    
    def get_statistics(self):
        """获取任务统计信息"""
        total = len(self.__tasks)
        completed = sum(1 for t in self.__tasks if t.completed)
        pending = total - completed
        return {"总任务": total, "已完成": completed, "待办": pending}
    
    def __len__(self):
        """魔法方法:支持 len(manager) 获取任务总数"""
        return len(self.__tasks)
    
    def __str__(self):
        stats = self.get_statistics()
        return f"TaskManager({self.owner}) - 总任务:{stats['总任务']} 已完成:{stats['已完成']}"
    
    def __call__(self):
        """魔法方法:直接调用实例时显示待办清单"""
        self.show_pending()


# ========== 使用演示 ==========
if __name__ == "__main__":
    # 创建管理器
    my_manager = TaskManager("书到用时方恨少")
    
    # 添加任务
    my_manager.add_task("写一篇面向对象博客", 5)
    my_manager.add_task("回复读者评论", 3)
    my_manager.add_task("研究 asyncio 库", 4)
    my_manager.add_task("整理下周分享大纲", 4)
    
    # 查看待办
    my_manager()   # 因为实现了 __call__,可以直接调用
    
    # 完成一个任务
    my_manager.finish_task(0)   # 假设我们完成了索引 0 的任务
    
    # 再次查看待办
    my_manager.show_pending()
    
    # 查看统计
    print(my_manager)           # 调用了 __str__
    print(f"总任务数: {len(my_manager)}")  # 调用了 __len__

8.2 运行结果解读

运行上面的代码,你会看到类似这样的输出:

复制代码
已添加任务:[⏳] 写一篇面向对象博客 (优先级: 5)
已添加任务:[⏳] 回复读者评论 (优先级: 3)
已添加任务:[⏳] 研究 asyncio 库 (优先级: 4)
已添加任务:[⏳] 整理下周分享大纲 (优先级: 4)

书到用时方恨少 的待办清单(按优先级排序):
  0. [⏳] 写一篇面向对象博客 (优先级: 5)
  1. [⏳] 研究 asyncio 库 (优先级: 4)
  2. [⏳] 整理下周分享大纲 (优先级: 4)
  3. [⏳] 回复读者评论 (优先级: 3)

任务已完成:写一篇面向对象博客

书到用时方恨少 的待办清单(按优先级排序):
  0. [⏳] 研究 asyncio 库 (优先级: 4)
  1. [⏳] 整理下周分享大纲 (优先级: 4)
  2. [⏳] 回复读者评论 (优先级: 3)

TaskManager(书到用时方恨少) - 总任务:4 已完成:1
总任务数: 4

8.3 案例中体现了哪些面向对象概念?

概念 在代码中的体现
封装 TaskManager__tasks 列表是私有的,外部只能通过 add_taskfinish_task 等方法操作,保护了内部数据结构不被意外篡改。
属性与方法 Task 类有实例属性 descriptionprioritycompleted;有方法 mark_done
self 每个方法都通过 self 访问当前实例的属性。
魔法方法 __str__ 用于美化打印;__len__ 允许使用 len()__call__ 允许直接调用实例。
类与实例 TaskTaskManager 是两个类,我们通过它们创建了多个实例。

9. 🎯 总结与展望

通过本文,我们从思维方式的转变开始,一步步揭开了 Python 面向对象编程的面纱:

  • 思维转变:从"指挥动作"的面向过程,转向"指挥对象"的面向对象。
  • 封装思想:将数据和操作数据的方法捆绑,隐藏内部细节,暴露有限接口。这是写出健壮、可维护代码的基石。
  • 基础语法 :如何定义类、创建实例、使用 self、定义属性和方法。
  • 魔法方法__init____str____repr____len____call__ 等,让自定义类与 Python 内置行为无缝衔接。
  • 综合案例 :通过 TaskManager 巩固了所学知识,看到了面向对象在真实场景下的优雅。

面向对象不是银弹 ,对于一次性脚本或极其简单的任务,面向过程仍然清晰高效。但当代码规模膨胀、多人协作、需要长期维护时,面向对象提供的模块化、可扩展性和可维护性就会成为你的救命稻草。

理解了封装和类的构建,下一步我们就要进入更精彩的领域------继承多态。我们将看到如何通过继承复用代码,如何通过多态实现灵活的扩展。敬请期待下一篇!

如果你在实践过程中有任何疑问,或者有自己的心得想要分享,欢迎在评论区留言。我是书到用时方恨少!,我们下篇见!🚀


本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。

相关推荐
2301_803538952 小时前
如何高效批量删除SQL数据_使用脚本分段删除降低压力
jvm·数据库·python
2401_897190552 小时前
MySQL升级导致排序规则变化怎么处理_更新Collation配置
jvm·数据库·python
冰暮流星2 小时前
javascript案例-简易计算器
开发语言·javascript·ecmascript
zhangchaoxies2 小时前
uni-app怎么动态生成二维码 uni-app利用插件生成分享码方法【技巧】
jvm·数据库·python
2402_854808372 小时前
如何在可视化编辑器中回滚错误的结构修改_通过事务或备份快速恢复元数据
jvm·数据库·python
Rsun045512 小时前
5、Java 原型模式从入门到实战
java·开发语言·原型模式
天若有情6732 小时前
原创C++设计模式:功能归一化——无继承、轻量版AOP,比传统OOP更优雅
开发语言·c++·设计模式·oop
FrontAI2 小时前
Next.js从入门到实战保姆级教程:实战项目(上)——全栈博客系统架构与核心功能
开发语言·前端·javascript·react.js·系统架构
Metaphor6922 小时前
使用 Python 压缩 PDF 文件的大小
python·pdf