Python类的定义与使用
简单7走进Python面向对象编程:用“模具”理解类,用“保险箱”理解封装
你有没有用月饼模具做过月饼?只要一个模具,就能压出许多花纹相同的月饼。但每个月饼的馅儿可以不同,比如豆沙、莲蓉、五仁。在Python里,**类(Class)**就像这个模具,它规定了月饼的形状和花纹;**对象(Object)**就是用一个模具做出来的一个个具体月饼,每个对象可以有自己的“馅儿”(数据)。
面向对象编程(OOP)就是让我们用“类”来创建“对象”,把数据(属性)和操作数据的功能(方法)打包在一起。这样代码可以反复使用,也更贴近真实世界。接下来我们会一步步学习:怎么定义类、怎么保护数据(封装)、怎么让类之间继承(继承)、怎么让同名方法在不同对象上表现出不同行为(多态)。
一、定义类与创建对象:用蛋糕模具做蛋糕
1. 什么是类和对象?
- 类:蓝图或模具,描述一类事物共有的属性和行为。比如“蛋糕”类有口味、大小属性,还有切蛋糕、吃蛋糕的方法。
- 对象:根据类造出的具体实例。比如一个“巧克力口味、大号”的蛋糕,就是一个对象。
2. 如何定义一个类?
用 class 关键字,类名一般首字母大写。类里可以写两种东西:
- 属性:描述对象特征的变量(如口味、大小)。
- 方法:对象能做的动作(函数)。
特别地,__init__ 是一个特殊方法,叫“构造方法”或“初始化方法”。当创建对象时,它会自动执行,用来给新对象设置初始属性。里面的 self 代表当前正在创建的对象本身——你不需要手动传它,Python会自动传入。
# 定义一个蛋糕类
class Cake:
# 初始化方法,创建蛋糕时自动调用
def __init__(self, flavor, size):
self.flavor = flavor # 属性:口味(字符串)
self.size = size # 属性:大小(字符串)
# 方法:切蛋糕
def cut(self, pieces):
print(f"把{self.size}的{self.flavor}蛋糕切成{pieces}块")
# 创建两个蛋糕对象(调用 __init__ 并传入参数)
cake1 = Cake("巧克力", "大") # 创建一个巧克力大蛋糕
cake2 = Cake("草莓", "小") # 创建一个草莓小蛋糕
# 调用对象的方法
cake1.cut(8) # 输出:把大的巧克力蛋糕切成8块
cake2.cut(4) # 输出:把小草莓蛋糕切成4块
注意:每个对象都保存自己的属性值,cake1.flavor 是 “巧克力”,cake2.flavor 是 “草莓”。self 就是用来区分不同对象的。
3. 新手最容易犯的错误
| 错误类型 | 错误写法 | 正确做法 |
|---|---|---|
忘记 self 参数 | def cut(self, pieces): 写成 def cut(pieces): | 方法第一个参数必须是 self |
在 __init__ 外定义属性 | 直接在方法里写 self.color = "白色" 而不在 __init__ 中初始化 | 建议所有属性在 __init__ 中定义,避免创建对象后属性不存在 |
| 构造函数名写错 | def __int__(self): 注意是 __init__ 不是 __int__ | 双下划线 + init + 双下划线 |
忘记写 self. | 方法内直接用 flavor 而不是 self.flavor | 调用自己的属性必须用 self.属性名 |
二、封装:把数据锁进保险箱
假设你有一个存钱罐,里面的钱是你的秘密,别人不能随便拿。但存钱罐上有一个小口,可以投币进去,还有一个密码锁,只有你知道密码才能打开取钱。在Python里,封装就是给数据加上这样的保护:把属性设为私有,只提供公开的方法来安全地访问和修改。
1. 如何设置私有属性?
在属性名前面加两个下划线 __,例如 self.__score。这样外部就不能直接用 对象.__score 来访问了,Python 会在内部把名字改成 _类名__score 来隐藏。
2. 用“getter”和“setter”安全操作
“getter”是获取值的方法(像从保险箱窥视口看余额),“setter”是修改值的方法(像用密码打开保险箱)。在 setter 里我们可以做检查,防止数据被乱改。
class Student:
def __init__(self, name, score):
self.name = name # 公开属性:姓名
self.__score = score # 私有属性:成绩(外部不能直接访问)
# 获取成绩的方法(getter)
def get_score(self):
return self.__score
# 修改成绩的方法(setter),只能在合理范围内修改
def set_score(self, new_score):
if 0 <= new_score <= 100:
self.__score = new_score
else:
print("成绩必须在0到100之间")
# 使用
s = Student("小明", 85)
print(s.get_score()) # 正确方式:85
s.set_score(95) # 修改成功
s.set_score(200) # 打印错误:成绩必须在0到100之间
# print(s.__score) # 这行会报错,因为 __score 是私有的
生活中的例子:游戏角色血量。你不想让其他代码随意把血量改成 99999,所以设置私有属性,并通过 setter 限制值只能在 0 到 1000 之间。
class Player:
def __init__(self, name):
self.name = name
self.__hp = 100 # 私有属性:初始血量100
def get_hp(self):
return self.__hp
def set_hp(self, new_hp):
if 0 <= new_hp <= 1000:
self.__hp = new_hp
else:
print("血量超出范围!")
3. 常见错误
- 直接访问私有属性:
s.__score在类外部会报错,必须通过 getter/setter。 - 忘记写双下划线:只写单下划线
_score表示“保护”但不是私有,外部仍可访问,但 Python 约定不要直接碰。 - setter 里没做检查:如果不检查,封装就失去了保护意义。
三、继承:孩子继承父母的特点
你从父母那里继承了很多特点,比如发色、身高,还能学习他们的技能。在Python里,继承让一个类(子类)自动拥有另一个类(父类)的属性和方法,还可以添加自己的新功能,或者改写(重写)父类的某些方法。
1. 基本语法
子类名后加 (父类名) 就表示继承。子类可以:
- 使用父类已有的属性和方法(不用重复写)
- 添加自己独有的属性和方法
- 重写父类的方法(同名覆盖)
2. 使用 super() 调用父类的初始化
子类自己的 __init__ 需要先调用父类的 __init__ 来初始化继承来的属性,用 super().__init__(参数) 完成。
# 父类:交通工具
class Vehicle:
def __init__(self, brand):
self.brand = brand # 品牌
def move(self):
print(f"{self.brand} 正在移动")
# 子类:汽车,继承了交通工具
class Car(Vehicle):
def __init__(self, brand, doors):
super().__init__(brand) # 调用父类的初始化,设置品牌
self.doors = doors # 子类独有的属性:车门数
# 子类自己的方法
def honk(self):
print(f"{self.brand} 汽车在鸣喇叭")
# 子类:自行车
class Bicycle(Vehicle):
def __init__(self, brand, type):
super().__init__(brand)
self.type = type # 自行车类型:山地、公路等
def ring(self):
print(f"{self.brand} 自行车在按铃")
# 使用
my_car = Car("宝马", 4)
my_car.move() # 继承自父类的方法
my_car.honk() # 子类自己的方法
my_bike = Bicycle("捷安特", "山地")
my_bike.move() # 继承自父类的方法
my_bike.ring() # 子类自己的方法
看到没?汽车和自行车没有写 move 方法,但它们都能调用 move,因为从 Vehicle 那里继承来了。这就是代码复用。
3. 方法重写(override)
如果子类想改变父类某个方法的行为,可以在子类中定义一个同名方法。例如,给汽车一个更具体的 move:
class Car(Vehicle):
# ... 省略其他
def move(self):
print(f"{self.brand} 汽车正在公路上行驶")
这样 my_car.move() 输出的就不是“宝马 正在移动”,而是“宝马 汽车正在公路上行驶”。
4. 常见错误
- 忘记调用
super().__init__:如果父类__init__有参数,子类不调用会导致父类属性未设置,报错“没有属性”。 - 方法重写时参数不匹配:重写的方法可以改变参数,但一般保持相同签名更合理。
- 混淆“继承”和“包含”:继承是“是一个”关系(Car 是一种 Vehicle),不是“拥有”关系(Car 有 Engine)。
四、多态:同一个命令,不同反应
想象你有一个万能遥控器,按“声音”按钮,不同设备反应不同:音响播放音乐、电视切换频道、空调调节风速。在Python里,多态就是让不同的类拥有同名的方法,然后我们可以用统一的方式调用它们,具体执行哪个版本由对象的类型决定。
1. 简单例子:动物叫声
class Animal:
def speak(self):
pass # 父类不实现,留给子类自己定义
class Dog(Animal):
def speak(self):
return "汪汪"
class Cat(Animal):
def speak(self):
return "喵喵"
class Duck(Animal):
def speak(self):
return "嘎嘎"
# 定义一个函数,接收任何动物,调用它的 speak 方法
def make_sound(animal):
print(animal.speak())
# 多态:同一个函数,不同对象表现不同
make_sound(Dog()) # 输出:汪汪
make_sound(Cat()) # 输出:喵喵
make_sound(Duck()) # 输出:嘎嘎
函数 make_sound 根本不管传入的是什么动物,它只要求这个对象有 speak 方法。这就是“相同的接口,不同的实现”。
2. 多态通常与继承一起用
子类重写父类的方法,然后通过父类类型的变量(或列表)存放不同子类对象,调用同一方法时自动执行子类版本。
animals = [Dog(), Cat(), Duck()]
for a in animals:
print(a.speak()) # 每个动物输出自己的叫声
3. 更多例子:形状计算面积
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
shapes = [Rectangle(3, 4), Circle(5)]
for s in shapes:
print(s.area()) # 12.0 和 78.5
4. 常见错误
- 方法名不一致:多态要求所有类使用完全相同的方法名(比如
speak),如果写成talk或speak就会失败。 - 混淆重写(override)和重载(overload):Python 不支持方法重载(同一方法不同参数),多态依赖的是重写。
- 忘记父类方法:如果父类没有定义
speak,调用时可能出错,所以通常父类定义一个空方法或抛出异常(pass或raise NotImplementedError)。
五、完整示例:学校人员管理系统
综合运用类、封装、继承、多态,写一个简单的学校人员管理程序。包括:
- 基类
Person:有姓名、年龄、私有属性__id和 getter/setter。 - 子类
Student:增加成绩属性,重写__init__和introduce方法。 - 子类
Teacher:增加课程属性,重写introduce方法。 - 多态:将不同人的
introduce统一调用。
# 基类:人
class Person:
def __init__(self, name, age, person_id):
self.name = name # 姓名
self.age = age # 年龄
self.__id = person_id # 私有属性:身份证号
# 获取身份证号
def get_id(self):
return self.__id
# 设置身份证号(简单演示,实际应加验证)
def set_id(self, new_id):
self.__id = new_id
# 自我介绍
def introduce(self):
print(f"我叫{self.name},今年{self.age}岁")
# 子类:学生
class Student(Person):
def __init__(self, name, age, person_id, score):
super().__init__(name, age, person_id) # 调用父类初始化
self.score = score # 成绩
# 重写父类的 introduce
def introduce(self):
print(f"我是学生{self.name},成绩{self.score}分")
# 子类:老师
class Teacher(Person):
def __init__(self, name, age, person_id, subject):
super().__init__(name, age, person_id)
self.subject = subject # 所教科目
def introduce(self):
print(f"我是{self.subject}老师{self.name}")
# 创建对象
stu = Student("小华", 12, "123456", 95)
tea = Teacher("张老师", 30, "987654", "数学")
# 多态:用同一个循环,每个人自我介绍
people = [stu, tea]
for p in people:
p.introduce()
# 输出:
# 我是学生小华,成绩95分
# 我是数学老师张老师
# 封装的体现:身份证号不能直接访问,但可通过方法获取
print(stu.get_id()) # 123456
# print(stu.__id) # 报错,私有属性无法直接访问
这个例子把之前学到的三个特性都融合了:
- 封装:
__id被保护,只有通过get_id和set_id才能操作。 - 继承:
Student和Teacher都继承了Person的name、age属性以及introduce方法,并进行了重写。 - 多态:
p.introduce()能自动调用对应子类的方法,不需要判断类型。
六、相关指引
- 想深入类? 可以学习类变量(
class_variable)和实例变量的区别、静态方法@staticmethod、类方法@classmethod。 - 想要更简洁的封装? Python 提供了
@property装饰器,让你像访问属性一样调用 getter 和 setter。 - 继承更多技巧? 了解多重继承、
MRO(方法解析顺序)、抽象类(abc模块)。 - 多态进阶? 学习“鸭子类型”——只要对象有你需要的方法,就算不是同一个类也能用多态。
把这些概念学扎实,你就能写出像乐高积木一样灵活、可复用的 Python 代码。现在,打开你的编程环境,动手试试自己设计一个类吧!
例题精讲
关于Python类的定义,以下说法正确的是?
执行以下代码,输出结果为? class MyClass: def __init__(self, value): self.value = value def show(self): return self.value obj = MyClass(42) print(obj.show())
在Python中,类方法(classmethod)的第一个参数是类本身,通常命名为cls。
Python中,类的私有属性可以通过在属性名前加双下划线(__)来定义,并且不能从外部访问。
请补全以下代码,使得类Person拥有一个实例方法greet,该方法打印"Hello, my name is [name]"。其中name属性在__init__中初始化。
class Person:
def __init__(self, name):
self.name = name
def greet(self):
print("Hello, my name is " + ___)