Python第六课:从零理解面向对象

Python第六课:从零理解面向对象

文章目录

引言

如果你做过蛋糕,一定知道模具的作用:一按一扣,形状就出来了,不用每个蛋糕都手工捏
Python 里的 类(class) 就是那个模具,对象(object) 就是烤出来的蛋糕
想批量生产结构相似的数据?想给每个数据都配上专属的函数?用面向对象就对了
本文会用最生活化的例子,带你亲手做一个“模具”,再做出几个“蛋糕”,并在这个过程中彻底搞懂 Python 的面向对象基础


在开始之前先检查一下你的装备吧!!!

python环境不会装的看这里从安装到Hello World:Python环境搭建完整指南

python编辑器不会装的看这里零基础Python入门:手把手教你安装Python、新版PyCharm和VS Code


为什么需要面向对象?

先看一个小需求:
你的程序里要记录两个学生的姓名和语文、数学成绩,并且能打印每个学生的总分,以及判断是否及格


面向过程的写法

你可能会这样写:

# 用两个字典存学生信息 student1 ={'name':'小明','chinese':85,'math':92} student2 ={'name':'小红','chinese':78,'math':88}# 写一个函数计算总分deftotal_score(student):return student['chinese']+ student['math']# 写一个函数判断是否及格(两科均≥60)defis_pass(student):return student['chinese']>=60and student['math']>=60# 使用print(student1['name'], total_score(student1), is_pass(student1))print(student2['name'], total_score(student2), is_pass(student2))

这段代码有什么问题?

  1. 数据和操作是分离的
    字典只存数据,函数在外面操作字典。如果以后要增加一个“英语”字段,所有函数都得改,还得小心别漏掉
  2. 代码散乱
    学生一多,字典定义散落在各处,函数也越堆越多,维护起来像在翻垃圾堆
  3. 没有约束
    调用 total_score 时,你传一个完全不相关的字典进去,比如 {'a':1, 'b':2},程序不会报错,但结果毫无意义——这不是“学生”,只是恰好有三个键
  4. 重复代码
    如果想把学生信息传给另一个模块,你得把所有相关函数一起拷过去,否则对方没法操作

用面向对象的写法

把“学生”这个概念抽象成一个,数据和操作都装在里面:

classStudent:def__init__(self, name, chinese, math): self.name = name self.chinese = chinese self.math = math deftotal_score(self):return self.chinese + self.math defis_pass(self):return self.chinese >=60and self.math >=60# 创建对象 s1 = Student('小明',85,92) s2 = Student('小红',78,88)# 使用print(s1.name, s1.total_score(), s1.is_pass())print(s2.name, s2.total_score(), s2.is_pass())

这个版本解决了什么问题?

  • 数据和操作在一起
    总分、及格判断都长在学生这个类里面,逻辑上就是一个整体
  • 扩展方便
    要加“英语”成绩,只需要在 __init__ 里加一个参数,然后把 total_scoreis_pass 改一下——所有用到 Student 的地方自动生效
  • 代码即文档
    Student 类本身就告诉了你:一个学生必须有姓名、语文、数学成绩,并且可以算总分、判及格。意图清晰
  • 复用简单
    想把学生管理功能拿到另一个项目,直接把整个 Student 类复制过去就行,不用东拼西凑

📌 这就是面向对象的核心思想

把相关数据和操作打包成一个整体(类),然后用这个整体去创建具体个体(对象)

你不再关心“我该怎么操作这些散乱的字典”,而是问“这个对象能帮我做什么”。代码的组织方式从 “函数处理数据” 变成了 “对象提供服务”

听起来有点抽象?没关系,接下来我们就一步步从零开始,教你亲手写出这样的类。你会发现,它比你想的要简单得多


什么是面向对象?

1. 先聊聊 面向过程

在彻底搞懂面向对象之前,我们需要先认识一下它的“前任”:面向过程编程


🔧 面向过程:按步骤办事

面向过程的核心思想是:把一件事拆成若干个步骤,然后用函数把每个步骤封装起来,按顺序调用

举个最生活的例子:做番茄炒蛋

面向过程的思路是这样:

  1. 准备食材(番茄、鸡蛋、盐)
  2. 打蛋、切番茄
  3. 热锅、倒油
  4. 炒蛋、盛出
  5. 炒番茄、加蛋、加盐
  6. 装盘

写成代码就是:

defprepare():print('准备食材')defcut():print('切番茄、打鸡蛋')deffry():print('先炒蛋,再炒番茄,混合调味')defserve():print('装盘上桌') prepare() cut() fry() serve()

特点

  • 关注的是 “做什么”“怎么做”
  • 程序 = 数据结构 + 算法
  • 代码以函数为单位组织,数据在函数间传来传去

2. 面向对象

面向对象换了个角度:我不关心步骤,我关心 “谁来做”

同样做番茄炒蛋,面向对象会这样想:

  • 有一个 厨师 角色
  • 厨师有自己的技能(切菜、炒菜、调味)
  • 厨师使用 食材(番茄、鸡蛋、调味料)
  • 最后生产出 菜肴(番茄炒蛋)

写成代码就是:

classChef:defcut(self, food):print(f'切{food}')deffry(self,*ingredients):print(f'炒{"、".join(ingredients)}')defserve(self, dish_name):print(f'{dish_name}装盘')classIngredient:def__init__(self, name): self.name = name # 创建对象 chef = Chef() tomato = Ingredient('番茄') egg = Ingredient('鸡蛋') salt = Ingredient('盐')# 厨师干活 chef.cut(tomato.name) chef.cut(egg.name) chef.fry(tomato.name, egg.name, salt.name) chef.serve('番茄炒蛋')

特点

  • 关注的是 “谁有责任做这件事”
  • 程序 = 一堆对象 + 对象之间的交互
  • 数据和操作它的方法打包在同一个类里

3. 面向过程 vs 面向对象

我也看到过一种解释方法说:面向过程是编年体; 面向对象是纪传体

对比维度面向过程面向对象
核心思想按步骤解决问题按角色分配责任
组织单位函数类 / 对象
数据与操作分离(数据在函数间传递)聚合(数据和操作封装在一起)
核心概念函数、变量类、对象、属性、方法
代码复用函数复用类复用(继承、组合)
适用场景小型、简单、线性流程(如脚本、工具函数)大型、复杂、多人协作(如Web后端、游戏、GUI)
优点简单直接,性能开销小易维护、易扩展、更贴近现实世界
缺点代码一长就混乱,难以维护学习曲线稍陡,设计成本较高

4. 举个例子

🌰 再举个对比:学生成绩管理

面向过程风格:

students =[{'name':'小明','score':85},{'name':'小红','score':92}]defadd_student(students, name, score): students.append({'name': name,'score': score})defavg_score(students):returnsum(s['score']for s in students)/len(students) add_student(students,'小刚',78)print(avg_score(students))

面向对象风格:

classStudent:def__init__(self, name, score): self.name = name self.score = score classClass:def__init__(self): self.students =[]defadd(self, student): self.students.append(student)defaverage(self):returnsum(s.score for s in self.students)/len(self.students) cls = Class() cls.add(Student('小明',85)) cls.add(Student('小红',92)) cls.add(Student('小刚',78))print(cls.average())

对比之下,面向对象的版本把“学生”和“班级”都变成了有明确职责的对象,代码的可读性和扩展性明显更好


5. 小结

🎯 那是不是面向对象就一定更好?

那肯定不是

  • 如果你只是在写几十行的小脚本,用面向过程完全没问题,更直接、更快
  • 一旦程序规模变大、需求频繁变化、或者需要多人协作,面向对象的优势就会体现出来——更容易扩展、更容易测试、更容易让不同的人分工开发不同类

现代开发通常是混合使用:顶层用面向对象组织架构,底层某些复杂算法仍然用面向过程的函数来实现

  • 面向过程:以“步骤”为中心,适合线性、简单的任务
  • 面向对象:以“角色”为中心,适合复杂、长期维护的系统
  • 两者不是对立关系,而是不同层次的工具。学会面向对象,不是要抛弃面向过程,而是给你的工具箱里多添一把利器

类与对象:模具与产品

面向对象里最核心的两个概念就是 类(Class)对象(Object)
很多人一开始会被这两个词绕晕,其实用一个生活比喻就能立刻搞懂


🍰 类比:蛋糕模具与蛋糕

想象一下你开了一家甜品店,想批量生产蛋糕

  • 你不可能每次烤蛋糕都从零捏形状——太慢,而且每个蛋糕形状都不一样
  • 你会先做一个模具:这个模具固定了蛋糕的大小、形状、厚度
  • 然后你用这个模具,倒面糊、进烤箱,做出一个个蛋糕

在这个场景里:

  • 模具 → 类:它是抽象的模板,定义了产品长什么样
  • 蛋糕 → 对象:它是具体的产品,按照模具生产出来的实物

模具只有一个,蛋糕可以有无数个
类在代码里只定义一次,但你可以用它创建任意多个对象


1. 创建你的第一个类

我们用一个最简单的例子:Dog 类

classDog:pass

就这么简单,一个类定义好了

  • class 是定义类的关键字
  • Dog 是类名,建议首字母大写,采用大驼峰命名法(每个单词首字母大写,如 BigDog, GoldenRetriever
  • pass 是占位符,表示这个类暂时什么都不做
  • 类名后面紧跟:占位符前面要有4个空格(按一下TAB键,效果一样)

2. 用类创建对象

模具做好之后,就可以生产蛋糕了:

my_dog = Dog()# 创建第一个对象 your_dog = Dog()# 创建第二个对象
  • my_dogyour_dog 都是 Dog 类的对象(也叫实例
  • 每调用一次 Dog(),Python 就会在内存里新分配一块空间,给你一个全新的、独立的对象

验证一下:

print(my_dog)print(your_dog)

输出(你的内存地址可能不同):

看到了吗?两个不同的内存地址,说明是两个不同的对象


3. 类与对象的本质区别

类(Class)对象(Object)
角色模具产品
数量1个无数个
本质代码里的定义内存里的实体
是否占内存不占(加载时占用极小代码段)每个对象都占独立内存
能否直接操作不能直接用来干活能调用方法、访问属性

一句话总结:类是创建对象的蓝图,对象是类的具体表现


🧪 试试看

在你的编辑器里运行这段完整代码:

classDog:pass dog1 = Dog() dog2 = Dog()print(dog1)print(dog2)print(type(dog1))# <class '__main__.Dog'>

你会看到两个不同地址的对象,它们的类型都是 Dog


4. 小结

📌 本节核心

  1. 是抽象模板,对象是具体实例
  2. class 类名: 定义类,类名首字母大写
  3. 类名() 创建对象,每调用一次生成一个新对象
  4. 类是代码层面的定义,对象是运行时在内存里的实体

现在的 Dog 类还是个空壳子,什么属性和方法都没有。下一节我们给它装上属性(名字、年龄),再配上动作(叫、跑),让它真正“活”起来


💬 小思考
如果我用 Dog() 创建了 100 个对象,内存里会同时存在几个 Dog 类?

(答案:1 个类定义,100 个对象实例)

给类添加属性

上一节我们做的 Dog 类还是个空模具,只能看出“这是一个狗类”,但每条狗叫什么名字、多大年纪、什么品种——这些具体的数据都存不进去

没有数据的对象,就像一个没装蛋糕的模具,只是个空壳
这一节我们就来给类装上属性,让每个对象都有自己的“身份信息”


🏷️ 什么是属性?

属性就是对象身上的数据
比如一条狗,它有名字、年龄、毛色;一辆车,它有品牌、颜色、排量

在 Python 里,属性就是依附在对象上的变量


🛠️ 第一种方式:直接给对象挂属性(不推荐)

你可以在创建对象之后,像挂钥匙一样直接给它加上属性:

classDog:pass my_dog = Dog() my_dog.name ='旺财'# 给对象挂一个 name 属性 my_dog.age =2# 再挂一个 age 属性print(my_dog.name)# 旺财print(my_dog.age)# 2

这确实能用,但非常不推荐,因为:

  • 每只狗都得手动一条条挂属性,代码重复
  • 如果忘记给某只狗挂 name,后面调用 print(dog.name) 就直接报错
  • 同一个类的不同对象,属性可能不一致(有的狗有 age,有的没有)

这种方式的唯一用途是临时调试,正式写代码请跳过它


正确方式:用 init 方法定义实例属性

Python 的类里有一个特殊方法叫 __init__(前后各两个下划线),在创建对象时会自动执行

我们把初始化属性的代码写在这里,以后每创建一个对象,__init__ 就会被调用一次,自动给新对象装上属性

classDog:def__init__(self, name, age): self.name = name # 给当前对象添加 name 属性 self.age = age # 给当前对象添加 age 属性

用这个类创建对象

dog1 = Dog('旺财',2) dog2 = Dog('来福',3)print(dog1.name, dog1.age)# 旺财 2print(dog2.name, dog2.age)# 来福 3

发生了什么?

  1. Dog('旺财', 2) 执行时,Python 自动调用 __init__ 方法
  2. self 就是刚刚被创建出来的那个对象(比如 dog1
  3. self.name = name 表示把参数 '旺财' 存进 dog1 对象里,作为它的 name 属性
  4. self.age = age 同理

为什么叫 self
你可以理解成“我自己”。谁调用这个方法,self 就是谁
这是约定俗称的写法,强烈建议不要改成其他名字


🧪 完整可运行示例

classDog:def__init__(self, name, age): self.name = name self.age = age # 创建两只狗 dog1 = Dog('旺财',2) dog2 = Dog('来福',3)# 访问属性print(f'{dog1.name} 今年 {dog1.age} 岁')print(f'{dog2.name} 今年 {dog2.age} 岁')

输出:

在这里插入图片描述

🧬 实例属性 vs 类属性

除了每个对象独有的实例属性,还有一种类属性——它是属于整个类的,所有对象共享

定义方式: 直接写在类内部,不在任何方法里

classDog:# 类属性 species ='哺乳动物'def__init__(self, name, age): self.name = name # 实例属性 self.age = age # 实例属性

使用:

dog1 = Dog('旺财',2) dog2 = Dog('来福',3)print(dog1.name)# 旺财(实例属性)print(dog2.name)# 来福(实例属性)print(dog1.species)# 哺乳动物(类属性)print(dog2.species)# 哺乳动物(类属性)print(Dog.species)# 哺乳动物(通过类名直接访问)

内存图理解

  • 实例属性:每个对象单独保存一份,互不影响
  • 类属性:只在类里保存一份,所有对象都指向同一个

🔍 实例属性 vs 类属性:对比表格

对比项实例属性类属性
定义位置__init__ 或其他实例方法中,用 self.属性名直接在类内部,方法外部
归属属于具体对象属于类本身
存储每个对象独立存储一份类内存空间只存一份
访问方式必须通过对象访问可通过对象或类名访问
修改影响只影响当前对象通过类名修改,影响所有对象
典型用途描述对象特有的数据(姓名、年龄)描述类别共有特征(物种、常量)

⚠️ 一个常见的坑

类属性可以通过对象访问,但如果你通过对象给类属性赋值,并不会修改类属性,而是给这个对象新建了一个同名的实例属性

classDog: species ='哺乳动物' dog1 = Dog() dog2 = Dog() dog1.species ='鸟类'# 这里!不是修改类属性,而是给 dog1 创建了实例属性print(dog1.species)# 鸟类(实例属性)print(dog2.species)# 哺乳动物(类属性,没变)print(Dog.species)# 哺乳动物(类属性,确实没变)

结论

  • 修改类属性请用类名.属性 = 新值
  • 不要通过对象给类属性赋值——那通常不是你想要的效果

小结

  1. 实例属性:通过 self.属性名 = 值__init__ 中定义,每个对象独立拥有
  2. 类属性:直接在类中定义,所有对象共享一份
  3. __init__ 方法在创建对象时自动执行,是最常用的属性初始化场所
  4. self 是对象自身,是连接方法内部和对象属性的桥梁

💬 小思考

如果我在 __init__ 里写 self.name = name,又在类的最上面写 name = '狗'(类属性),然后创建对象时传入 '旺财',那么对象的 name 是多少?类属性 name 会被覆盖吗?

(答案:对象的 name'旺财';类属性 name 依然存在且值为 '狗',两者互不干扰。)

给类添加方法

有了属性的对象,就像有了身份证,能知道“它是谁、几岁了”
但一条狗不能只是躺着——它得会叫、会跑、会吃饭,对吧?

这些“行为”在面向对象里就叫方法(Method)
方法是定义在类里的函数
,专门为这个类的对象服务


🎬 什么是方法?

简单说:写在类里面的函数,就叫方法

但方法和普通函数有一个关键区别:方法必须有一个参数self,代表调用这个方法的对象本身

看个最简版本:

classDog:def__init__(self, name): self.name = name defbark(self):# 这是一个方法print('汪汪汪!')
  • bark 是一个方法,它没有接收外部参数,只有 self
  • 当调用 dog1.bark() 时,Python 会自动把 dog1 传给 self,所以你调用时不用传 self

📞 调用方法

classDog:def__init__(self, name): self.name = name defbark(self):print(f'{self.name}:汪汪汪!') my_dog = Dog('旺财') my_dog.bark()# 旺财:汪汪汪!

过程解析

  1. my_dog.bark() → Python 翻译成 Dog.bark(my_dog)
  2. 方法内部的 self.name 就是 my_dog.name,也就是 '旺财'
  3. 打印出旺财:汪汪汪!

这就是 self 的魔法:它让方法知道自己是为哪个对象服务的


🎯 方法可以访问和修改实例属性

方法内部不仅可以读属性,还可以改属性:

classDog:def__init__(self, name, age): self.name = name self.age = age self.hungry =True# 刚出生都饿着defeat(self): self.hungry =Falseprint(f'{self.name} 吃饱了')defbark(self):print(f'{self.name} 叫了一声') dog = Dog('旺财',2)print(dog.hungry)# True dog.eat()# 旺财 吃饱了print(dog.hungry)# False

eat 方法修改了 hungry 属性,对象的状态发生了变化


🧮 方法也可以有自己的参数

除了 self,方法可以像普通函数一样接受额外参数:

classDog:def__init__(self, name): self.name = name defbark(self, times):for _ inrange(times):print(f'{self.name}:汪!') dog = Dog('来福') dog.bark(3)# 叫三声

输出:


🧠 方法 vs 函数:一张表看懂

对比点普通函数实例方法
定义位置模块中类内部
第一个参数任意必须是 self(约定)
调用方式函数名(参数)对象.方法名(参数)
能否直接访问对象属性不能(需传对象)能(通过 self)
归属属于模块属于类/对象

🧪 完整示例:让狗跑起来

classDog:def__init__(self, name, speed=5): self.name = name self.speed = speed self.position =0defrun(self, seconds): distance = self.speed * seconds self.position += distance print(f'{self.name} 跑了 {distance} 米,现在位置在 {self.position} 米处')defbark(self):print(f'{self.name}:汪汪!我在位置 {self.position}') dog = Dog('闪电',8) dog.run(3)# 闪电 跑了 24 米,现在位置在 24 米处 dog.bark()# 闪电:汪汪!我在位置 24 dog.run(2)# 闪电 跑了 16 米,现在位置在 40 米处

这个例子展示了:

  • 实例属性(name, speed, position
  • 实例方法(run, bark
  • 方法内部操作属性(读取、修改)

Read more

Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战

Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战 前言 在进行 Flutter for OpenHarmony 的大规模异步业务系统(如实时行情刷新、多源数据聚合)开发时,如何更优雅地处理 Future 的超时竞争、Stream 的防抖(Debounce)或复杂的并发队列控制?虽然 Dart async 包提供了基础功能,但 async_extension 进一步扩展了异步编程的边界,提供了更符合函数式范式的工具。本文将探讨如何在鸿蒙端构建极致、高效的异步处理链路。 一、原直观解析 / 概念介绍 1.1 基础原理 该库通过对 Dart 核心异步类的非侵入式扩展(Extensions)

By Ne0inhk
计算机毕业设计必看必学~基于springboot大学生实习管理系统的设计与实现,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!

计算机毕业设计必看必学~基于springboot大学生实习管理系统的设计与实现,原创定制程序、单片机、java、PHP、Python、小程序、文案全套、毕设成品等!

springboot大学生实习管理系统 摘要 随着大学生实习的日益重要和广泛普及,建立一套高效、便捷的大学生实习管理系统对于高校和学生都具有重要意义。本文基于Spring Boot框架,设计并开发了一套大学生实习管理系统,旨在提供一个全面、可靠的平台,方便学生、教师和企业进行实习管理。 该系统采用了前后端分离的架构,前端使用Vue.js技术栈开发,后端使用Spring Boot框架搭建。系统实现了学生信息管理、实习岗位发布与申请、实习评分等功能。学生可以通过系统查看自己的实习信息、提交实习报告和评价,并与企业和指导老师进行沟通交流。教师可以管理学生的实习任务、审核实习报告以及评定实习成绩。企业可以发布实习岗位、筛选学生申请,并进行实习任务的管理和评价。 在系统实现过程中,充分考虑了用户体验和安全性。通过合理的权限控制机制,确保不同角色用户只能访问其具备权限的功能。同时,系统也提供了数据备份和恢复功能,保障数据的安全性和可靠性。 实际测试结果表明,该系统具有良好的稳定性和可用性,满足了大学生实习管理的需求。用户对系统的易用性和功能完善性给予了积极评价。 综上所述,本文设计和开

By Ne0inhk
vue3+python基于python的球类体育赛事发布和在线购票选座系统60576715

vue3+python基于python的球类体育赛事发布和在线购票选座系统60576715

目录 * 技术栈与项目概述 * 系统架构设计 * 数据库模型设计 * 前端关键技术实现 * 后端核心逻辑 * 安全与性能优化 * 测试与部署 * 扩展方向 * 开发技术路线 * 源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式! 技术栈与项目概述 * 前端框架: Vue 3 (Composition API + TypeScript) * 后端语言: Python (FastAPI/Django 选型分析) * 数据库: PostgreSQL/MySQL 与 Redis 缓存 * 核心功能: 赛事发布、在线选座购票、支付集成、实时数据更新 系统架构设计 * 前后端分离: RESTful API 接口设计规范 * 微服务模块划分: 用户服务、赛事管理、订单支付、座位库存 * WebSocket 应用: 实时推送座位锁定状态与赛事更新

By Ne0inhk