目录
一. 为什么要有面向对象?
二. 类和对象:图纸与实物的关系
2.1 给对象添加属性
2.2 self到底是什么?
2.3 给对象添加方法
三. 类属性和实例属性
四. 访问限制:有些东西不能随便改
4.1 Python中的私有属性
4.2 那我怎么查看余额?
4.3 那怎么修改余额?
4.4 为什么不直接改?非要绕一圈?
4.5 私有属性真的完全访问不到吗?
4.6 小结
五. 继承:别重复造轮子
5.1 super()是什么?
5.2 为什么需要super()?
5.3 super()就是找爸爸帮忙
5.4 可以不用super()吗?
5.5 super()最常见的使用场景
5.6 小结
六. 多态:同一个命令,不同执行结果
6.1 多态真正厉害的地方
6.2 用生活中的例子理解多态
6.3 为什么需要多态?
6.4 Python中的多态更自由
6.5 小结
七. 获取对象信息:看看对象到底是谁
7.1 type():查看对象类型
7.2 isinstance():判断对象是不是某个类
7.3 dir():查看对象拥有的全部能力
7.4 小结
八. 魔法方法:让对象变得更聪明
8.1 为什么需要魔法方法?
8.2 str():自定义对象输出内容
8.3 len():让对象支持len()
8.4 魔法方法为什么叫魔法方法?
8.5 小结
总结:什么是面向对象?
学 Python 学到这里,很多同学都会遇到一个让人头大的知识点:
面向对象编程(Object Oriented Programming,简称 OOP)
光是名字就已经劝退一半人了。
再看到什么:
-
类(Class)
-
对象(Object)
-
继承(Inheritance)
-
多态(Polymorphism)
不少人当场进入懵圈状态。
实际上,面向对象并没有想象中那么复杂。
如果用一句话来概括:
面向对象就是用代码描述现实世界中的事物。
今天我们就用大白话把 Python 面向对象编程讲明白。
一. 为什么要有面向对象?
假设现在要记录一个学生的信息:
name = "张三"
age = 18
score = 95
第二个学生:
name2 = "李四"
age2 = 20
score2 = 88
第三个学生:
name3 = "王五"
age3 = 19
score3 = 92
刚开始还好。
如果有100个学生呢?
你的代码会变成大型灾难现场。
所以程序员想到一个办法:
能不能把“学生”这个概念单独抽出来?
于是就有了:
类(Class)
二. 类和对象:图纸与实物的关系
类可以理解成:
图纸
对象可以理解成:
根据图纸生产出来的实物
例如:
class Student:
pass
这里就创建了一个学生类。
然后根据这个类创建对象:
s1 = Student()
s2 = Student()
这时候:
Student
是图纸。
而:
s1
s2
是真正的学生对象。
就像:
汽车设计图
↓
宝马
奔驰
奥迪
图纸不是车。
车是根据图纸造出来的。
2.1 给对象添加属性
现实中的学生肯定有自己的信息:
-
姓名
-
年龄
-
成绩
那么对象也需要保存这些数据。
Python 提供了一个特殊方法:
__init__()
很多新手第一次看到这个名字的时候都会怀疑:
程序员是不是按键盘的时候手抽筋了?
其实它叫:
构造方法
创建对象时自动执行。
例如:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
创建对象:
s1 = Student("张三", 18)
print(s1.name)
print(s1.age)
输出:
张三
18
2.2 self到底是什么?
这是面向对象里最容易把小白绕晕的东西。
其实很简单。
self
表示:
当前对象自己
例如:
s1 = Student("张三",18)
这里:
self
指的就是:
s1
如果:
s2 = Student("李四",20)
那么:
self
指的就是:
s2
你可以把 self 理解成:
“我自己”
就不会觉得奇怪了。
2.3 给对象添加方法
对象不仅有属性。
还可以有行为。
比如学生可以介绍自己:
class Student:
def __init__(self,name):
self.name = name
def introduce(self):
print("大家好,我叫",self.name)
使用:
s1 = Student("张三")
s1.introduce()
输出:
大家好,我叫 张三
这里的:
introduce()
就是对象的方法。
三. 类属性和实例属性
先看一个例子:
class Student:
school = "清华大学"
这里的:
school
叫做类属性。
所有对象共享。
s1 = Student()
s2 = Student()
print(s1.school)
print(s2.school)
输出:
清华大学
清华大学
而:
self.name
属于实例属性。
每个对象都有自己的值。
s1 = Student("张三")
s2 = Student("李四")
此时:
s1.name
和
s2.name
完全独立。
简单记:
-
类属性:大家共用
-
实例属性:自己独有
四. 访问限制:有些东西不能随便改
先来看一个例子。
假设你正在开发一个银行系统。
每个用户都有自己的余额:
class Bank:
def __init__(self, money):
self.money = money
创建一个用户:
user = Bank(1000)
print(user.money)
输出:
1000
看起来没什么问题。
但是问题来了。
因为这个属性是公开的,所以任何人都能修改:
user.money = 100000000
print(user.money)
输出:
100000000
看到这里银行经理已经开始心脏疼了。
因为如果用户都能这样改余额:
user.money = 999999999999
那银行第二天就可以宣布破产了。
现实世界中也是一样。
比如:
- 银行余额
- 游戏角色等级
- 考试成绩
- 员工工资
这些数据都不能让别人随便修改。
所以面向对象提供了一个重要思想:
重要的数据要藏起来。
4.1 Python中的私有属性
在属性前面加两个下划线:
class Bank:
def __init__(self, money):
self.__money = money
这里的:
__money
就叫私有属性。
创建对象:
user = Bank(1000)
如果直接访问:
print(user.__money)
会报错:
AttributeError
因为 Python 会告诉你:
这是对象内部的数据,不允许直接访问。
4.2 那我怎么查看余额?
总不能连自己的钱都不知道吧?
所以通常会提供一个专门的方法:
class Bank:
def __init__(self, money):
self.__money = money
def get_money(self):
return self.__money
调用:
user = Bank(1000)
print(user.get_money())
输出:
1000
这样就能安全地获取余额。
4.3 那怎么修改余额?
同样不能直接改:
user.__money = 5000
而是通过专门的方法:
class Bank:
def __init__(self, money):
self.__money = money
def set_money(self, money):
self.__money = money
调用:
user.set_money(5000)
这样余额就被修改了。
4.4 为什么不直接改?非要绕一圈?
很多小白学到这里都会问:
直接写 user.money = 5000 不香吗?
确实香。
但是有风险。
例如银行规定:
余额不能是负数。
如果直接修改:
user.money = -1000000
程序根本拦不住。
但如果通过:
set_money()
就可以加判断:
class Bank:
def __init__(self, money):
self.__money = money
def set_money(self, money):
if money >= 0:
self.__money = money
else:
print("余额不能小于0")
测试:
user.set_money(-100)
输出:
余额不能小于0
这样数据就安全多了。
4.5 私有属性真的完全访问不到吗?
这里顺便提一句。
很多新手会发现:
print(user._Bank__money)
居然还能访问。
然后觉得:
Python骗我!
其实 Python 的私有属性更像是:
不建议访问,而不是绝对禁止访问。
属于一种程序员之间的默契:
这是我的内部数据
你可以强行看
但最好别看
正常开发中:
get_xxx()
set_xxx()
才是推荐方式。
4.6 小结
访问限制的核心思想其实很简单:
不让外部直接操作重要数据。
而是通过专门的方法进行访问和修改。
这样做的好处是:
- 数据更安全
- 更容易检查错误
- 更符合现实业务逻辑
说白了就是:
不是所有东西都应该让别人直接碰。
就像银行卡密码一样。
你当然知道它存在。
但银行绝不会把它贴在ATM机上。 😏
五. 继承:别重复造轮子
假设有一个动物类:
class Animal:
def eat(self):
print("吃东西")
狗会吃东西:
class Dog(Animal):
pass
猫也会吃东西:
class Cat(Animal):
pass
这样:
dog = Dog()
dog.eat()
就可以直接使用父类的方法。
这就是继承。
继承的核心思想:
能复用就复用。
程序员最讨厌重复代码。
因为复制粘贴一时爽。
后期维护火葬场。
5.1 super()是什么?
学习继承时,经常会看到这样一段代码:
class Animal:
def __init__(self, name):
self.name = name
然后创建一个狗类:
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
这里最神秘的部分就是:
super()
很多新手第一次看到它的时候都会想:
这是什么超人(Superman)函数吗?
其实它的作用很简单:
帮我们调用父类的方法。
5.2 为什么需要super()?
先看一个例子。
假设动物都有名字。
父类:
class Animal:
def __init__(self, name):
self.name = name
狗继承动物:
class Dog(Animal):
def __init__(self, name):
pass
创建对象:
dog = Dog("旺财")
看起来没问题。
但是当你访问:
print(dog.name)
就会报错。
因为:
class Dog(Animal):
def __init__(self, name):
pass
重写了父类的 __init__()。
此时程序执行流程变成:
创建Dog对象
↓
执行Dog自己的__init__()
↓
父类__init__()没有执行
↓
name根本没有创建
所以:
dog.name
找不到。
5.3 super()就是找爸爸帮忙
解决方法:
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
再次创建:
dog = Dog("旺财")
执行流程:
创建Dog对象
↓
执行Dog自己的__init__()
↓
super()
↓
执行Animal的__init__()
↓
创建self.name
↓
对象初始化完成
此时:
print(dog.name)
输出:
旺财
正常了。
5.4 可以不用super()吗?
当然可以。
例如:
class Dog(Animal):
def __init__(self, name):
Animal.__init__(self, name)
效果一样。
输出也一样。
很多新手会问:
那我直接写这个不就行了吗?
确实可以。
但是当继承关系复杂之后:
Animal
↓
Dog
↓
GoldenDog
或者:
Animal
↓
Dog
↓
PoliceDog
如果全部写死:
Animal.__init__()
后面维护会越来越麻烦。
而:
super()
会自动找到当前类对应的父类。
代码更灵活。
所以实际开发中几乎都使用:
super()
5.5 super()最常见的使用场景
最常见的情况就是:
子类想增加自己的属性,同时保留父类的属性。
例如:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, age):
super().__init__(name)
self.age = age
创建对象:
dog = Dog("旺财", 3)
此时:
print(dog.name)
print(dog.age)
输出:
旺财
3
如果不用:
super().__init__(name)
那么:
self.name
根本不会创建。
一个生活中的理解方式
假设父类是:
Animal
负责提供:
名字
年龄
体重
这些基础信息。
而子类:
Dog
除了这些基础信息之外,还想增加:
品种
这时候没必要把父类代码全部复制一遍。
直接:
super()
相当于说:
爸,这部分你先帮我处理。
我再补充自己的内容。
所以:
super().__init__()
你完全可以理解成:
先让父类干活。
子类再接着干活。
5.6 小结
super() 最核心的作用就是:
调用父类的方法。
最常见写法:
super().__init__()
作用:
- 继承父类初始化逻辑
- 避免重复代码
- 让代码更容易维护
对于初学者来说,可以先记住一句话:
super() 就是子类向父类“借能力”。
自己没有的功能,先找父类帮忙处理,然后再完成自己的工作。 😏
六. 多态:同一个命令,不同执行结果
前面我们学过继承。
例如:
class Animal:
def run(self):
print("动物在跑")
狗继承动物:
class Dog(Animal):
def run(self):
print("小狗在跑")
猫继承动物:
class Cat(Animal):
def run(self):
print("小猫在跑")
创建对象:
dog = Dog()
cat = Cat()
执行:
dog.run()
cat.run()
输出:
小狗在跑
小猫在跑
虽然调用的都是:
run()
但不同对象执行出了不同结果。
这就是多态最基本的体现。
6.1 多态真正厉害的地方
刚才的例子其实还不够明显。
因为我们知道:
dog
就是狗。
cat
就是猫。
下面来看一个更经典的例子。
定义一个函数:
def animal_run(animal):
animal.run()
注意这里。
函数根本不关心传进来的是什么。
它只负责:
animal.run()
调用即可。
传入狗对象:
animal_run(Dog())
输出:
小狗在跑
传入猫对象:
animal_run(Cat())
输出:
小猫在跑
你会发现:
同样的函数:
animal_run()
完全不用修改。
仅仅因为传入的对象不同。
结果就不同。
这才是多态最核心的思想。
6.2 用生活中的例子理解多态
假设有这样一个按钮:
播放
你点击按钮:
play()
如果播放的是音乐:
开始播放歌曲
如果播放的是电影:
开始播放电影
如果播放的是游戏:
启动游戏
你按的始终是同一个按钮。
调用的始终是:
play()
但不同对象执行出了不同结果。
这就是多态。
6.3 为什么需要多态?
假设没有多态。
你可能需要写:
if isinstance(animal, Dog):
animal.run()
elif isinstance(animal, Cat):
animal.run()
elif isinstance(animal, Pig):
animal.run()
elif isinstance(animal, Bird):
animal.run()
每增加一种动物。
代码就得修改一次。
程序员看了血压都会升高。
有了多态之后:
def animal_run(animal):
animal.run()
无论以后新增:
Dog
Cat
Pig
Bird
Tiger
甚至:
SuperDog
这个函数都不用改。
因为它只关心一件事:
你有没有 run() 方法。
至于你到底是什么动物。
它不关心。
6.4 Python中的多态更自由
很多编程语言要求:
必须继承同一个父类
才能实现多态。
而 Python 更随意。
例如:
class Car:
def run(self):
print("汽车在跑")
调用:
animal_run(Car())
输出:
汽车在跑
居然也能正常运行。
因为 Python 更看重:
你能不能做这件事。
而不是:
你到底是什么身份。
这种思想也经常被称为:
鸭子类型(Duck Typing)
意思是:
走起来像鸭子
叫起来像鸭子
那我就把你当鸭子。
6.5 小结
多态的核心不是:
子类重写父类方法。
而是:
同一个接口,不同对象表现出不同的行为。
例如:
run()
对于:
Dog
Cat
Car
都可以调用。
但执行结果各不相同。
这就是多态。
简单记一句:
继承解决代码复用,多态解决代码扩展。
当以后增加新功能时,我们往往只需要新增类,而不用修改原来的代码。
这也是面向对象编程最大的魅力之一。
七. 获取对象信息:看看对象到底是谁
写程序的时候,经常会遇到这样一种情况:
这个对象到底是什么类型?
或者:
这个对象有哪些属性和方法?
这时候 Python 提供了一些非常实用的工具。
它们就像对象的身份证和档案袋一样。
能够帮助我们快速了解一个对象的信息。
7.1 type():查看对象类型
例如:
class Student:
pass
s1 = Student()
print(type(s1))
输出:
<class '__main__.Student'>
说明:
s1
是一个 Student 类创建出来的对象。
不仅对象能查看。
数字、字符串、列表也能查看。
print(type(100))
print(type("hello"))
print(type([1,2,3]))
输出:
<class 'int'>
<class 'str'>
<class 'list'>
简单理解:
type()就是查看对象属于什么类型。
相当于查看身份证上的职业信息。
7.2 isinstance():判断对象是不是某个类
有时候我们不仅想知道类型。
还想判断:
这个对象是不是某个类创建出来的?
例如:
class Student:
pass
s1 = Student()
print(isinstance(s1, Student))
输出:
True
如果换成:
print(isinstance(100, Student))
输出:
False
继承场景下尤其有用。
class Animal:
pass
class Dog(Animal):
pass
创建对象:
dog = Dog()
判断:
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
输出:
True
True
因为:
Dog 本身也是 Animal 的一种。
7.3 dir():查看对象拥有的全部能力
假设你拿到一个对象。
完全不知道里面有什么。
这时候:
print(dir(s1))
就能查看对象所有属性和方法。
输出会很多:
['__class__',
'__dict__',
'__init__',
'__str__',
…
]
第一次看到可能会觉得:
这是什么天书?
其实不用慌。
它的作用就是告诉你:
这个对象有哪些功能。
很多时候忘记方法名了。
直接:
dir(对象)
看看就知道了。
7.4 小结
这三个工具是最常用的对象信息查看工具:
| type() | 查看对象类型 |
| isinstance() | 判断对象是否属于某个类 |
| dir() | 查看对象所有属性和方法 |
以后调试程序的时候会经常用到。
相当于给对象做一次体检。
八. 魔法方法:让对象变得更聪明
前面我们已经接触过:
__init__()
其实 Python 中还有很多类似的方法。
它们都有一个特点:
前后两个下划线
例如:
__init__
__str__
__len__
这种方法通常被称为:
魔法方法(Magic Method)
或者:
特殊方法(Special Method)
它们能够让对象拥有一些特殊能力。
8.1 为什么需要魔法方法?
来看一个例子。
创建学生类:
class Student:
def __init__(self, name):
self.name = name
创建对象:
s1 = Student("张三")
print(s1)
输出:
<__main__.Student object at 0x123456>
很多小白第一次看到都会想:
这打印的是地址还是乱码?
实际上这是对象在内存中的默认表示方式。
对于人类来说并不友好。
8.2 str():自定义对象输出内容
如果希望打印得更好看一点:
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Student:{self.name}"
再次打印:
print(s1)
输出:
Student:张三
是不是舒服多了?
以后只要执行:
print(对象)
Python就会自动调用:
__str__()
方法。
8.3 len():让对象支持len()
我们知道:
len("hello")
输出:
5
那么自己的对象能不能也支持呢?
当然可以。
class Student:
def __init__(self, name):
self.name = name
def __len__(self):
return len(self.name)
使用:
s1 = Student("张三")
print(len(s1))
输出:
2
这里 Python 自动调用了:
__len__()
方法。
8.4 魔法方法为什么叫魔法方法?
因为很多时候:
你明明没有主动调用它。
它却自动执行了。
例如:
创建对象:
Student("张三")
自动调用:
__init__()
打印对象:
print(s1)
自动调用:
__str__()
计算长度:
len(s1)
自动调用:
__len__()
看起来就像会魔法一样。
所以大家习惯称它们为:
魔法方法。
8.5 小结
魔法方法本质上就是:
Python预留好的一些特殊方法。
通过重写这些方法,我们可以让自己的对象拥有更多能力。
例如:
| init() | 创建对象时自动执行 |
| str() | 打印对象时自动执行 |
| len() | 调用len()时自动执行 |
对于初学者来说,记住最常见的:
__init__()
__str__()
就足够了。
以后随着学习深入,你还会接触更多有趣的魔法方法。
而这些魔法方法,也是 Python 面向对象编程中非常有特色的一部分。 😏
总结:什么是面向对象?
很多教程喜欢把面向对象总结成:
-
封装
-
继承
-
多态
这些没错。
但对于初学者来说,更简单的理解方式是:
用类描述事物,用对象表示具体事物。
例如:
类:学生
对象:
张三
李四
王五
通过属性描述特征:
姓名
年龄
成绩
通过方法描述行为:
学习
考试
自我介绍
这样代码会更加清晰、更容易维护。
当你的程序越来越大时,面向对象的优势就会越来越明显。
毕竟管理代码和管理房间是一个道理。
东西少的时候随便放。
东西多了以后,没有分类迟早会乱成一锅粥。






