CC++ & Algorithm

Python类的定义与使用

简单7
语言版本:C++Python
概述:用“蛋糕模具”比喻类,用“蛋糕”比喻对象,学习如何定义类并创建对象。

走进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),如果写成 talkspeak 就会失败。
  • 混淆重写(override)和重载(overload):Python 不支持方法重载(同一方法不同参数),多态依赖的是重写。
  • 忘记父类方法:如果父类没有定义 speak,调用时可能出错,所以通常父类定义一个空方法或抛出异常(passraise 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_idset_id 才能操作。
  • 继承StudentTeacher 都继承了 Personnameage 属性以及 introduce 方法,并进行了重写。
  • 多态p.introduce() 能自动调用对应子类的方法,不需要判断类型。

六、相关指引

  • 想深入类? 可以学习类变量(class_variable)和实例变量的区别、静态方法 @staticmethod、类方法 @classmethod
  • 想要更简洁的封装? Python 提供了 @property 装饰器,让你像访问属性一样调用 getter 和 setter。
  • 继承更多技巧? 了解多重继承、MRO(方法解析顺序)、抽象类(abc 模块)。
  • 多态进阶? 学习“鸭子类型”——只要对象有你需要的方法,就算不是同一个类也能用多态。

把这些概念学扎实,你就能写出像乐高积木一样灵活、可复用的 Python 代码。现在,打开你的编程环境,动手试试自己设计一个类吧!

例题精讲

1单选题

关于Python类的定义,以下说法正确的是?

A定义类时,类名后必须写父类
B类的方法的第一个参数必须是self
C类的__init__方法在实例化时自动调用
D类的属性必须在__init__方法中定义
2单选题

执行以下代码,输出结果为? class MyClass: def __init__(self, value): self.value = value def show(self): return self.value obj = MyClass(42) print(obj.show())

A42
BNone
C报错
D<function MyClass.show at ...>
3判断题

在Python中,类方法(classmethod)的第一个参数是类本身,通常命名为cls。

4判断题

Python中,类的私有属性可以通过在属性名前加双下划线(__)来定义,并且不能从外部访问。

5填空题
请补全以下代码,使得类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 " + ___)