资讯动态

Python类与对象详解:从第二十三课掌握面向对象核心

发布时间:2026/10/24 1:03:26 来源:尧图企业网站定制
1. 从“第二十三课”说起为什么“类”是绕不开的分水岭如果你正在跟着一套系统课程学编程翻到“第二十三课”这个位置大概率已经过了变量、循环、函数这几道坎。前面的内容虽然也有难点但基本属于“把一件事写清楚”的范畴。到了“类”这一课事情开始变得不一样了——你不再只是写一条条按顺序执行的指令而是开始描述“一类东西应该长什么样、能做什么”。这个转变是很多人学编程过程中第一个真正的分水岭。我见过不少朋友前面二十几课学得顺风顺水到“类”这里突然卡住不是语法看不懂而是脑子里没有建立起“用代码描述一个概念”的思维方式。他们能写出class Dog:这样的代码但说不清楚为什么要这么写更不知道什么时候该用类、什么时候不该用。所以这篇内容我想把“什么是类”这件事彻底讲透。不是照本宣科地念语法而是从“为什么需要类”这个根子上聊起把类的本质、构成、使用场景、常见误区、实操细节全部拆开揉碎。适合正在学第二十三课的你也适合学了类但一直用不利索、想回头补课的朋友。核心关键词就一个类。围绕它我们会聊到对象、属性、方法、实例化、封装这些配套概念但主线始终是回答“类到底是什么、怎么用、为什么这么用”。我个人的习惯是学任何新概念之前先问三个问题它解决什么问题没有它会怎样有了它之后代码发生了什么变化带着这三个问题往下看你会比死记语法的人快得多。2. 类的本质拆解它到底在描述什么2.1 从“一堆变量和函数”到“一个整体”假设你要写一个程序管理学生信息。不用类的话你可能会这么干student1_name A同学 student1_age 18 student1_score 92 student2_name B同学 student2_age 19 student2_score 85两个学生还能忍二十个学生呢两百个呢变量名会变成灾难而且你没法方便地把“一个学生的所有信息”作为一个整体传递、存储、处理。你可能会想到用字典student1 {name: A同学, age: 18, score: 92} student2 {name: B同学, age: 19, score: 85}这已经好多了数据被组织成了一个整体。但问题还在如果我想给每个学生加一个“计算等级”的行为怎么办只能再写一个独立的函数把字典传进去def get_level(student): if student[score] 90: return 优秀 elif student[score] 60: return 及格 else: return 不及格数据和操作数据的逻辑是分离的。字典里只有数据函数在外面飘着。这种写法在简单场景下没问题但一旦系统变大你会发现“学生相关的数据”和“学生相关的操作”散落在各处改一个地方要翻好几个文件。类的出现就是为了把“数据”和“操作数据的行为”绑在一起。用类改写之后class Student: def __init__(self, name, age, score): self.name name self.age age self.score score def get_level(self): if self.score 90: return 优秀 elif self.score 60: return 及格 else: return 不及格现在一个Student对象既知道自己叫什么、多大、考了多少分也知道自己该被评为什么等级。数据和行为成了一个整体。这就是类最核心的价值把相关的数据和操作封装在一起形成一个自洽的单元。2.2 类不是“数据”而是“数据的模板”初学者最容易混淆的一点是把类和对象当成一回事。这里必须掰清楚。类class是模板、是图纸、是概念。对象object也叫实例 instance是根据模板造出来的具体东西。打个比方Student这个类就像学校发的空白学生登记表。表格上规定了要填姓名、年龄、成绩还规定了“根据成绩算等级”的规则。但这张空白表本身不是任何一个学生。你拿这张表复印一份、填上“A同学、18、92”这份填好的表才是一个具体的对象。代码里体现得更清楚class Student: def __init__(self, name, age, score): self.name name self.age age self.score score s1 Student(A同学, 18, 92) s2 Student(B同学, 19, 85)Student是类s1和s2是两个独立的对象。它们都遵循Student这个模板的结构但各自的数据互不影响。改s1.name不会动到s2.name。提示判断一个东西是类还是对象最简单的办法是看它能不能直接“用”。类通常需要经过实例化调用一次才能得到一个可用的对象。Student是类Student(A同学, 18, 92)的结果才是对象。2.3__init__到底在干什么很多人对__init__的理解停留在“初始化方法”这个标签上但说不清它和普通方法的区别。我用一句话概括__init__是对象被创建时自动执行的“装配说明书”。当你写s1 Student(A同学, 18, 92)时发生了这么几件事系统先创建一个空的Student对象。自动调用__init__方法把这个空对象作为self传进去。同时把你写的A同学、18、92分别传给name、age、score。__init__内部执行self.name name等赋值把数据“装”到对象身上。装配完成后把对象返回给s1。所以self不是关键字它只是“当前这个对象”的代号。你完全可以写成this或者别的名字但 Python 社区约定俗成用self换了名字别人看着别扭不建议标新立异。这里有个实操细节值得注意__init__不是“创建对象”的方法对象在调用__init__之前就已经被创建出来了。__init__只负责往这个空壳里填东西。真正创建对象的是__new__但那是更底层的机制日常写业务代码基本用不到第二十三课这个阶段不需要深究。2.4 属性对象身上挂着的“数据口袋”属性就是对象身上存储数据的变量。在类内部通过self.xxx定义和访问在类外部通过对象名.xxx访问。s1 Student(A同学, 18, 92) print(s1.name) # A同学 print(s1.score) # 92 s1.score 95 # 直接修改属性 print(s1.score) # 95属性分两种实例属性和类属性。实例属性是每个对象各自一份的比如name、age、scoreA同学和B同学的名字不一样。类属性是挂在类上的、所有对象共享的class Student: school 某中学 # 类属性 def __init__(self, name, age, score): self.name name self.age age self.score scoreschool是类属性所有Student对象都能读到它而且改一次类属性所有对象看到的都变。但要注意如果某个对象写了s1.school 另一所学校那只是给s1这个对象单独加了一个实例属性不会影响其他对象也不会改到类属性本身。这个坑我在实际项目里见过好几次排查半天才发现是有人不小心在实例上覆盖了类属性。2.5 方法对象会做的“动作”方法是定义在类里面的函数第一个参数固定是self。通过对象名.方法名()调用时Python 会自动把对象本身作为self传进去。class Student: def __init__(self, name, score): self.name name self.score score def get_level(self): if self.score 90: return 优秀 elif self.score 60: return 及格 return 不及格 s1 Student(A同学, 92) print(s1.get_level()) # 优秀注意s1.get_level()括号里什么都没写但方法定义里有一个self。这个self就是s1自己。方法内部通过self.score拿到的是s1的成绩不是别人的。这就是“方法能操作自己对象的数据”的含义。方法让对象有了“行为”。没有方法的类只是一个数据结构有了方法对象才真正成为一个能干活的东西。这也是类和字典最本质的区别——字典只能存数据类能存数据还能定义行为。3. 为什么非得用类几个真实场景的对比3.1 场景一多个同类数据的批量管理假设你在做一个成绩统计的小工具需要处理一个班的学生。用字典列表的写法students [ {name: A同学, score: 92}, {name: B同学, score: 85}, {name: C同学, score: 58}, ] def get_level(score): if score 90: return 优秀 elif score 60: return 及格 return 不及格 for s in students: print(s[name], get_level(s[score]))用类的写法class Student: def __init__(self, name, score): self.name name self.score score def get_level(self): if self.score 90: return 优秀 elif self.score 60: return 及格 return 不及格 students [Student(A同学, 92), Student(B同学, 85), Student(C同学, 58)] for s in students: print(s.name, s.get_level())两种写法都能跑输出一样。区别在哪当需求变复杂时类的优势会迅速放大。比如要给每个学生加“加分”“扣分”“查历史成绩”“导出报告”这些功能用字典的话每个功能都是一个独立函数参数传来传去用类的话这些功能都是学生自己的方法调用起来是s.add_score(5)、s.export_report()语义清晰代码组织也更自然。3.2 场景二需要保证数据一致性假设学生成绩必须在 0 到 100 之间。用字典的话你没法阻止别人写student[score] 999只能在每次用之前手动检查。用类的话可以在方法里做校验class Student: def __init__(self, name, score): self.name name self.set_score(score) def set_score(self, score): if not 0 score 100: raise ValueError(成绩必须在0到100之间) self.score score这样所有修改成绩的入口都收口到set_score方法里校验逻辑只写一次不会漏。这就是封装带来的好处把数据保护起来只通过受控的接口访问。当然 Python 没有强制私有属性但通过方法收口是一种约定团队协作时非常有用。3.3 场景三需要多个“变种”共享基础能力假设你不只管理学生还要管理老师。老师和学生都有姓名、年龄但老师有工号、教的科目学生有成绩、班级。如果各写各的姓名年龄这些重复逻辑要写两遍。用类继承可以解决class Person: def __init__(self, name, age): self.name name self.age age def introduce(self): return f我叫{self.name}今年{self.age}岁 class Student(Person): def __init__(self, name, age, score): super().__init__(name, age) self.score score class Teacher(Person): def __init__(self, name, age, subject): super().__init__(name, age) self.subject subjectStudent和Teacher都继承了Person自动拥有name、age和introduce方法不用重复写。这就是类在代码复用上的威力。第二十三课可能还没讲到继承但你现在就应该知道类不只是“把数据和函数打包”它还是构建复杂系统的基本积木继承、多态这些高级特性都建立在类的基础上。3.4 什么时候不该用类说了这么多类的好处也得说说什么情况别硬上类。我见过一些初学者学了类之后看什么都想封装成类结果写出一堆只有一个方法、没有任何状态的“类”纯粹是给代码加了一层没必要的壳。判断标准很简单如果一组数据总是成组出现并且有明确的行为依附于它们就用类如果只是临时传几个参数、算一个结果用函数就够了。比如写一个add(a, b)求两数之和没必要搞一个Calculator类。但如果是一个计算器有历史记录、有当前值、有撤销功能那用类就合适。提示不要为了用类而用类。类是组织代码的工具不是目的。工具用对了地方才有价值。4. 手把手实操从零写一个能用的类4.1 需求定义先想清楚要描述什么我们拿一个“购物车”作为实操案例。需求是一个购物车能添加商品、移除商品、计算总价、显示清单。每个商品有名称和单价。先别急着写代码用大白话把需求捋一遍商品有名字、有单价。购物车能装多个商品能加能减能算总价能列出所有商品。这里其实有两个概念商品和购物车。商品适合用一个类描述购物车适合用另一个类描述。购物车里装的是商品对象。这种“一个类里包含另一个类的对象”的结构在实际开发中非常常见。4.2 第一步定义商品类class Item: def __init__(self, name, price): self.name name self.price price def __str__(self): return f{self.name}{self.price}元__str__是一个特殊方法定义“打印这个对象时显示什么”。不定义的话打印出来是一串看不懂的内存地址。定义了之后print(item)会输出人类可读的内容。这个技巧在实际开发中很实用调试的时候一眼就能看清对象里是什么。4.3 第二步定义购物车类class Cart: def __init__(self): self.items [] def add(self, item): self.items.append(item) def remove(self, name): for item in self.items: if item.name name: self.items.remove(item) return True return False def total(self): return sum(item.price for item in self.items) def show(self): if not self.items: print(购物车是空的) return for item in self.items: print(item) print(f总计{self.total()}元)几个细节值得说self.items []在__init__里初始化保证每个购物车对象都有自己独立的商品列表。如果写成类属性items []所有购物车会共享同一个列表这是新手常踩的坑。remove方法按名称查找并删除找到返回True没找到返回False让调用方知道操作结果。total用了生成器表达式简洁高效。show处理了空购物车的情况避免打印一片空白让人困惑。4.4 第三步跑起来看看cart Cart() cart.add(Item(苹果, 5)) cart.add(Item(香蕉, 3)) cart.add(Item(牛奶, 12)) cart.show() # 苹果5元 # 香蕉3元 # 牛奶12元 # 总计20元 cart.remove(香蕉) cart.show() # 苹果5元 # 牛奶12元 # 总计17元到这里一个能用的购物车就完成了。你可以继续扩展加数量、加折扣、加优惠券、持久化到文件。每加一个功能都是往对应的类里加方法结构清晰不会乱。4.5 参数选择与命名规范写类的时候命名有几个约定俗成的规矩遵守了代码可读性会好很多元素命名风格示例类名大驼峰ShoppingCart、StudentInfo方法名小写加下划线get_total、add_item属性名小写加下划线user_name、total_price私有属性前缀单下划线_internal_cache常量全大写MAX_SIZE这些不是强制的但整个 Python 社区都这么写你不这么写别人读起来会别扭。特别是类名用大驼峰这一条几乎是铁律看到student小写开头有经验的人会下意识以为这是个变量或函数而不是类。提示属性名不要和内置函数重名比如list、dict、type。虽然语法上允许但会覆盖内置名字后面想用内置功能时会出问题。5. 常见问题与排查技巧实录5.1 忘记写 self 导致报错这是最高频的错误没有之一。症状是调用方法时报TypeError: xxx() takes 0 positional arguments but 1 was given或者方法内部访问属性时报NameError: name xxx is not defined。原因通常是定义方法时漏了self参数或者访问属性时漏了self.前缀。比如class Student: def __init__(self, name): name name # 错误没有 self. self.name name # 正确第一行name name只是把参数赋值给参数自己对象上根本没有name属性。后面访问self.name就会报错。排查方法检查每个方法定义的第一个参数是不是self检查每个属性访问是不是都带了self.。5.2 可变默认参数的坑这个问题在函数那一课就该讲但在类里同样常见class Cart: def __init__(self, items[]): # 危险 self.items items这样写的话所有不传items的Cart对象会共享同一个列表。一个购物车加了东西另一个购物车也会看到。正确写法是用None做默认值class Cart: def __init__(self, itemsNone): self.items items if items is not None else []这个坑非常隐蔽因为单独测试一个对象时完全正常只有创建多个对象时才会暴露。我建议养成习惯只要默认值是列表、字典、集合这类可变对象一律用None代替在函数体内再创建。5.3 类属性和实例属性搞混前面提过类属性是所有对象共享的实例属性是各自独立的。看这个例子class Counter: count 0 def __init__(self): Counter.count 1 a Counter() b Counter() print(Counter.count) # 2这里count是类属性每次创建对象都通过Counter.count修改类属性本身所以能正确计数。但如果写成self.count 1情况就变了self.count 1等价于self.count self.count 1会先读类属性然后创建一个同名的实例属性类属性根本没变。这是很多人写计数器时踩的坑。5.4 常见问题速查表报错信息可能原因解决方法TypeError: xxx() takes 0 positional arguments but 1 was given方法定义漏了self在方法第一个参数加上selfAttributeError: Xxx object has no attribute yyy属性没在__init__里定义或拼写错误检查__init__是否赋值了该属性检查拼写NameError: name xxx is not defined方法内访问属性漏了self.加上self.前缀多个对象数据互相影响用了可变类属性或可变默认参数改为在__init__里创建实例属性打印对象显示内存地址没定义__str__或__repr__添加__str__方法返回可读字符串子类初始化父类属性失败没调用super().__init__()在子类__init__里调用父类初始化5.5 调试类的小技巧调试类相关问题时我习惯在__init__末尾加一句print(f创建了 {self})配合定义好的__str__能清楚看到对象是什么时候被创建的、创建了几个。排查“对象莫名多出来”或“对象没被创建”这类问题时特别有效。另一个技巧是用vars(obj)查看对象身上所有的实例属性返回一个字典。有时候你以为是属性没赋值一看vars才发现是名字写错了。dir(obj)则能看到对象能访问的所有属性和方法包括从类继承来的排查“这个方法到底存不存在”时很好用。提示vars()和dir()是内置函数任何对象都能用不需要额外导入。调试阶段随手用比打断点还快。6. 从类到对象思维一次认知升级学完第二十三课你掌握的不只是一个语法结构而是一种新的组织代码的方式。以前你写代码是“第一步做什么、第二步做什么”是线性的、流程式的。现在你开始思考“这个系统里有哪些角色、每个角色有什么数据和行为、角色之间怎么交互”这是面向对象思维的开端。我个人的体会是从“函数思维”转到“对象思维”最难的不是语法而是习惯。刚开始你会觉得用类写代码比用函数麻烦定义一堆东西才能跑起来。但当你写的程序超过几百行、需要多人协作、需要反复修改扩展时类带来的结构清晰度和可维护性会成倍回报你。最后分享一个我常用的练习方法拿你身边任何一个东西试着用类描述它。比如你的手机它有什么属性品牌、颜色、电量有什么方法开机、关机、充电。写出来实例化几个不同型号的手机看看它们怎么各自独立工作。这种练习做上十几次类的思维就长在你脑子里了。后续你还会学到继承、多态、魔术方法、设计模式这些全都建立在今天这一课的基础上。把类吃透后面的路会顺很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑