1. 引言
很多初学者在掌握 Python 基础语法后,都会卡在同一个问题上:如何从「写脚本」进阶到「写程序」?答案就是面向对象编程(OOP)。本文正是为已经掌握 Python 基础语法、希望系统学习面向对象思想的读者而写,帮你彻底搞懂类与对象、封装、继承、多态这三大特性,以及 __init__、__str__、__add__ 等魔术方法背后的运行机制。
读完本文,你将能够:独立设计并定义自己的类,用封装保护数据安全,用继承实现代码复用,用多态写出更灵活的代码,并通过魔术方法让自定义类拥有与内置类型一致的使用体验。全文配有大量可直接运行的示例和综合实战,跟着敲一遍,你就能真正把面向对象用起来。
Python 从设计之初就是一门面向对象的语言,它的很多内置类型(如 int、str、list)本质上都是类。掌握类与对象、三大特性(封装、继承、多态)以及魔术方法,是写出结构清晰、易于维护的 Python 代码的关键一步。
2. 类与对象基础
2.1 什么是类,什么是对象
类(Class) 是对象的蓝图或模板,它定义了一组属性和方法。对象(Object) 是根据类创建出来的具体实例。
举个例子:Dog 是一个类,它定义了所有狗共有的特征(名字、年龄)和行为(叫、跑);而 小白 是 Dog 类的一个具体对象。
2.2 定义类与创建对象
使用 class 关键字定义类,使用 类名() 创建对象:
class Dog:
"""狗类"""
# 类属性:所有实例共享
species = "犬科"
# 构造方法:创建对象时自动调用
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age
# 实例方法
def bark(self):
print(f"{self.name} 在汪汪叫!")
def run(self):
print(f"{self.name} 跑得飞快!")
# 创建对象
dog1 = Dog("小白", 2)
dog2 = Dog("大黄", 3)
# 访问属性
print(dog1.name) # 小白
print(dog1.species) # 犬科(类属性)
# 调用方法
dog1.bark() # 小白 在汪汪叫!
dog2.run() # 大黄 跑得飞快!
2.3 self 是什么
self 代表实例本身。在类内部定义方法时,第一个参数必须是 self(名字可以改,但约定俗成用 self)。调用实例方法时,Python 会自动把对象本身传给 self,所以调用时不需要手动传这个参数。
2.4 类属性与实例属性
- 类属性:定义在类内部、方法外部,所有实例共享。
- 实例属性:在 __init__ 中通过 self.xxx 定义,每个实例独立拥有。
class Student:
school = "第一中学" # 类属性
def __init__(self, name):
self.name = name # 实例属性
s1 = Student("小明")
s2 = Student("小红")
print(s1.school) # 第一中学
print(s2.school) # 第一中学
3. 封装
3.1 什么是封装
封装(Encapsulation) 是指将数据(属性)和操作数据的方法绑定在一起,并对外隐藏内部实现细节,只暴露必要的接口。封装的好处是:保护数据不被随意修改,提高代码的安全性和可维护性。
3.2 私有属性与方法
在 Python 中,约定使用单下划线 _ 表示「受保护」成员,使用双下划线 __ 表示「私有」成员。双下划线开头的属性或方法,在类外部不能直接访问(Python 会做名称改写)。
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # 私有属性
def deposit(self, amount):
"""存款"""
if amount > 0:
self.__balance += amount
print(f"存入 {amount} 元,余额 {self.__balance} 元")
else:
print("存款金额必须大于 0")
def withdraw(self, amount):
"""取款"""
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"取出 {amount} 元,余额 {self.__balance} 元")
else:
print("余额不足或金额非法")
def get_balance(self):
"""对外提供只读接口"""
return self.__balance
acc = BankAccount("张三", 1000)
acc.deposit(500) # 存入 500 元,余额 1500 元
acc.withdraw(200) # 取出 200 元,余额 1300 元
print(acc.get_balance()) # 1300
# 直接访问私有属性会报错
# print(acc.__balance) # AttributeError
3.3 使用 @property 实现优雅的封装
@property 装饰器可以把方法变成属性来访问,既能保护数据,又能保持代码简洁:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""获取半径"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径(带校验)"""
if value <= 0:
raise ValueError("半径必须大于 0")
self._radius = value
@property
def area(self):
"""计算面积(只读属性)"""
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.53975
c.radius = 10 # 走 setter 校验
print(c.area) # 314.159
# 传入非法值(负数或非数字)时,捕获 ValueError 并给出友好提示
try:
c.radius = –3
except ValueError as e:
print(f"设置失败:{e}") # 设置失败:半径必须大于 0
try:
c.radius = "abc"
except ValueError as e:
print(f"设置失败:{e}") # 设置失败:半径必须大于 0
下面用一张表格对比 _ 单下划线、__ 双下划线和 @property 三种封装方式的差异:
| _ 单下划线 | 类外部可访问,仅作「约定」提示,不强制限制 | 表示「受保护」成员,供类内部及子类使用,避免误用 | self._radius = radius |
| __ 双下划线 | 类外部不能直接访问(Python 会做名称改写) | 表示「私有」成员,防止外部随意修改,需通过方法间接访问 | self.__balance = balance |
| @property | 通过属性语法访问,可在 getter/setter 中加校验逻辑 | 对外暴露「看似普通属性」的接口,同时保护数据、保持代码简洁 | @property def radius(self): … |
4. 继承
4.1 什么是继承
继承(Inheritance) 允许一个类(子类)获取另一个类(父类)的属性和方法,从而实现代码复用。子类可以继承父类的所有公开成员,也可以添加自己的新成员,或重写父类的方法。
4.2 基本继承语法
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} 正在吃东西")
def sleep(self):
print(f"{self.name} 正在睡觉")
class Dog(Animal): # 继承 Animal
def bark(self):
print(f"{self.name} 在汪汪叫")
class Cat(Animal): # 继承 Animal
def meow(self):
print(f"{self.name} 在喵喵叫")
dog = Dog("小白")
cat = Cat("小花")
dog.eat() # 小白 正在吃东西(继承自父类)
dog.bark() # 小白 在汪汪叫(子类自己的方法)
cat.sleep() # 小花 正在睡觉(继承自父类)
cat.meow() # 小花 在喵喵叫
4.3 方法重写与 super()
子类可以重写父类的方法。在重写时,通常用 super() 调用父类的同名方法,以复用父类逻辑:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} 发出声音")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类构造方法
self.breed = breed
def speak(self): # 重写父类方法
print(f"{self.name}({self.breed})在汪汪叫")
dog = Dog("小白", "金毛")
dog.speak() # 小白(金毛)在汪汪叫
4.4 多继承
Python 支持多继承,即一个子类可以继承多个父类:
class Flyable:
def fly(self):
print("我会飞!")
class Swimmable:
def swim(self):
print("我会游泳!")
class Duck(Flyable, Swimmable):
pass
duck = Duck()
duck.fly() # 我会飞!
duck.swim() # 我会游泳!
5. 多态
5.1 什么是多态
多态(Polymorphism) 是指同一个方法调用,在不同对象上表现出不同的行为。多态让代码更加灵活——我们不需要关心对象的具体类型,只要它拥有我们调用的方法即可。
5.2 多态的典型实现
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪汪")
class Cat(Animal):
def speak(self):
print("喵喵喵")
class Duck(Animal):
def speak(self):
print("嘎嘎嘎")
# 多态:同一个函数,处理不同类型的对象
def make_speak(animal):
animal.speak()
animals = [Dog(), Cat(), Duck()]
for animal in animals:
make_speak(animal)
# 输出:
# 汪汪汪
# 喵喵喵
# 嘎嘎嘎
5.3 鸭子类型
Python 的多态更多体现在「鸭子类型」上:只要一个对象拥有某个方法,就可以调用它,而不必关心它属于哪个类。
class Car:
def run(self):
print("汽车在跑")
class Bike:
def run(self):
print("自行车在骑")
def start(obj):
obj.run() # 只要对象有 run 方法即可
start(Car()) # 汽车在跑
start(Bike()) # 自行车在骑
6. 魔术方法
6.1 什么是魔术方法
魔术方法(Magic Methods) 也叫「特殊方法」或「双下划线方法」,是 Python 中以双下划线开头和结尾的方法(如 __init__、__str__)。它们由 Python 在特定场景下自动调用,用来定制类的行为。
6.2 常用魔术方法一览
| __init__(self, …) | 创建对象时 | 构造方法,初始化实例属性 |
| __str__(self) | print(obj) / str(obj) | 返回人类可读的字符串 |
| __repr__(self) | 交互式环境直接输入对象 | 返回开发者可读的字符串 |
| __len__(self) | len(obj) | 返回长度 |
| __getitem__(self, key) | obj[key] | 按下标取值 |
| __setitem__(self, key, value) | obj[key] = value | 按下标赋值 |
| __iter__(self) | for x in obj | 返回迭代器 |
| __eq__(self, other) | obj1 == obj2 | 判断相等 |
| __lt__(self, other) | obj1 < obj2 | 判断小于 |
| __add__(self, other) | obj1 + obj2 | 定义加法行为 |
| __call__(self, …) | obj(…) | 让对象像函数一样被调用 |
6.3 实战示例:自定义一个「分数」类
下面综合运用多个魔术方法,实现一个支持加减比较的分数类:
class Fraction:
def __init__(self, numerator, denominator):
if denominator == 0:
raise ValueError("分母不能为 0")
self.numerator = numerator
self.denominator = denominator
def __str__(self):
"""print 时显示为 分子/分母"""
return f"{self.numerator}/{self.denominator}"
def __repr__(self):
return f"Fraction({self.numerator}, {self.denominator})"
def __add__(self, other):
"""支持分数 + 分数"""
new_num = self.numerator * other.denominator + other.numerator * self.denominator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __eq__(self, other):
"""支持 == 比较"""
return (self.numerator * other.denominator) == (other.numerator * self.denominator)
def __lt__(self, other):
"""支持 < 比较"""
return (self.numerator * other.denominator) < (other.numerator * self.denominator)
f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
print(f1) # 1/2(触发 __str__)
print(f1 + f2) # 5/6(触发 __add__)
print(f1 == Fraction(2, 4)) # True(触发 __eq__)
print(f1 < f2) # False(触发 __lt__)
接下来,我们看看如何对 Fraction 类进行异常处理。当分母为 0 时,构造方法会抛出 ValueError;当参与运算的对象不是 Fraction 类型时,会抛出 TypeError。下面用 try-except-finally 完整演示这两种异常的捕获与处理思路:
# 1. 捕获分母为 0 的 ValueError
try:
f = Fraction(1, 0) # 分母为 0,触发 ValueError
except ValueError as e:
print(f"创建失败:{e}") # 创建失败:分母不能为 0
finally:
print("无论是否出错,finally 都会执行") # 用于释放资源、收尾等
# 2. 捕获类型不符的 TypeError
try:
f1 = Fraction(1, 2)
result = f1 + 3 # 3 不是 Fraction,触发 TypeError
except TypeError as e:
print(f"运算失败:{e}") # 运算失败:unsupported operand type(s)…
finally:
print("运算流程结束")
# 3. 正常情况 + finally 收尾
try:
f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
print(f1 + f2) # 5/6,正常执行
except (ValueError, TypeError) as e:
print(f"出错了:{e}")
finally:
print("无论成功还是失败,finally 都会执行")
处理思路说明:
- ValueError:分母为 0 属于「业务逻辑上的非法值」,用 except ValueError 捕获,并给出友好提示,避免程序直接崩溃。
- TypeError:运算对象类型不符属于「类型层面的错误」,用 except TypeError 捕获,提示调用方传入正确的 Fraction 对象。
- finally:无论是否发生异常都会执行,适合做资源清理、日志记录等收尾工作;也可以把多个异常类型写在同一个 except 元组里统一处理。
6.4 让对象可迭代:__iter__ 与 __getitem__
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
"""返回迭代器,支持 for 循环"""
n = self.start
while n > 0:
yield n
n -= 1
for num in Countdown(5):
print(num, end=" ") # 5 4 3 2 1
7. 综合实战:员工管理系统
把本章知识串起来,实现一个简单的员工管理系统:
class Employee:
"""员工基类"""
def __init__(self, name, salary):
self.name = name
self.salary = salary
def work(self):
return f"{self.name} 正在工作"
def get_salary(self):
return self.salary
def __str__(self):
return f"{self.name}(月薪 {self.salary} 元)"
class Developer(Employee):
"""程序员:继承员工,重写 work"""
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def work(self):
return f"{self.name} 正在用 {self.language} 写代码"
def __add__(self, other):
"""两个程序员合并薪资(演示魔术方法)"""
return Developer(f"{self.name}&{other.name}", self.salary + other.salary, self.language)
class Manager(Employee):
"""经理:继承员工,重写 work"""
def __init__(self, name, salary, team_size):
super().__init__(name, salary)
self.team_size = team_size
def work(self):
return f"{self.name} 正在管理 {self.team_size} 人的团队"
# 多态:统一调用 work()
employees = [
Developer("张三", 20000, "Python"),
Developer("李四", 18000, "Java"),
Manager("王五", 35000, 10),
]
for emp in employees:
print(emp.work())
# 输出:
# 张三 正在用 Python 写代码
# 李四 正在用 Java 写代码
# 王五 正在管理 10 人的团队
# 魔术方法演示
dev1 = Developer("张三", 20000, "Python")
dev2 = Developer("李四", 18000, "Python")
team = dev1 + dev2
print(team) # 张三&李四(月薪 38000 元)
print(team.work()) # 张三&李四 正在用 Python 写代码
8. 总结
本章我们系统学习了 Python 面向对象编程的核心知识:
- 类与对象:类是模板,对象是实例;__init__ 负责初始化,self 代表实例本身。
- 封装:通过私有属性(__xxx)和 @property 保护数据,对外只暴露必要接口。
- 继承:子类复用父类代码,用 super() 调用父类方法,支持多继承。
- 多态:同一方法在不同对象上表现不同,配合鸭子类型让代码更灵活。
- 魔术方法:通过 __str__、__add__、__iter__ 等定制类的行为,让自定义类更贴近 Python 内置类型的使用体验。
面向对象是 Python 进阶的重要基石。下一篇我们将继续深入探讨更多高级特性,敬请期待!




